Passed
Push — master ( 2702d6...98524e )
by Arthur
05:33
created

BaseRelationsTrait::related()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 17
rs 9.9
c 0
b 0
f 0
cc 3
nc 4
nop 3
1
<?php
2
3
namespace SoliDry\Extension;
4
5
use Illuminate\Http\Request;
6
use Illuminate\Http\Response;
7
use League\Fractal\Resource\Collection;
8
use SoliDry\Blocks\FileManager;
9
use SoliDry\Helpers\Classes;
10
use SoliDry\Helpers\ConfigHelper;
11
use SoliDry\Helpers\Errors;
12
use SoliDry\Helpers\Json;
13
use SoliDry\Helpers\MigrationsHelper;
14
use SoliDry\Types\DirsInterface;
15
use SoliDry\Types\ModelsInterface;
16
use SoliDry\Types\PhpInterface;
17
use SoliDry\Types\ApiInterface;
18
use SoliDry\Helpers\ConfigHelper as conf;
19
20
/**
21
 * Trait BaseRelationsTrait
22
 * @package SoliDry\Extension
23
 */
24
trait BaseRelationsTrait
25
{
26
    use BaseModelTrait;
27
28
    /**
29
     * GET the relationships of this particular Entity
30
     *
31
     * @param Request $request
32
     * @param int|string $id
33
     * @param string $relation
34
     * @return Response
35
     */
36
    public function relations(Request $request, $id, string $relation) : Response
37
    {
38
        $model = $this->getEntity($id);
39
        if (empty($model)) {
40
            return $this->getResponse((new Json())->getErrors((new Errors())->getModelNotFound($this->entity, $id)),
0 ignored issues
show
Bug introduced by
It seems like getResponse() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

40
            return $this->/** @scrutinizer ignore-call */ getResponse((new Json())->getErrors((new Errors())->getModelNotFound($this->entity, $id)),
Loading history...
41
                JSONApiInterface::HTTP_RESPONSE_CODE_NOT_FOUND);
42
        }
43
44
        $resource = Json::getRelations($model->$relation, $relation);
45
        return $this->getResponse(Json::prepareSerializedRelations($request, $resource));
46
    }
47
48
    /**
49
     * GET the fully represented relationships of this particular Entity
50
     *
51
     * @param Request $request
52
     * @param int|string $id
53
     * @param string $relation
54
     * @return Response
55
     */
56
    public function related(Request $request, $id, string $relation) : Response
57
    {
58
        $data = ($request->input(ModelsInterface::PARAM_DATA) === null) ? ModelsInterface::DEFAULT_DATA
59
            : Json::decode(urldecode($request->input(ModelsInterface::PARAM_DATA)));
60
61
        $model = $this->getEntity($id);
62
        if (empty($model)) {
63
            return $this->getResponse((new Json())->getErrors((new Errors())->getModelNotFound($this->entity, $id)),
64
                JSONApiInterface::HTTP_RESPONSE_CODE_NOT_FOUND);
65
        }
66
67
        $relEntity = ucfirst($relation);
68
        $formRequestEntity  = $this->getFormRequestEntity(conf::getModuleName(), $relEntity);
0 ignored issues
show
Bug introduced by
It seems like getFormRequestEntity() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

68
        /** @scrutinizer ignore-call */ 
69
        $formRequestEntity  = $this->getFormRequestEntity(conf::getModuleName(), $relEntity);
Loading history...
69
        $relFormRequest = new $formRequestEntity();
70
71
        $resource = Json::getResource($relFormRequest, $model->$relation, $this->entity, true);
72
        return $this->getResponse(Json::prepareSerializedData($resource, $data));
73
    }
74
75
    /**
76
     * POST relationships for specific entity id
77
     *
78
     * @param Request $request
79
     * @param int|string $id
80
     * @param string $relation
81
     * @return Response
82
     */
83
    public function createRelations(Request $request, $id, string $relation) : Response
84
    {
85
        $model    = $this->presetRelations($request, $id, $relation);
86
        $resource = Json::getResource($this->formRequest, $model, $this->entity);
87
88
        return $this->getResponse(Json::prepareSerializedData($resource), JSONApiInterface::HTTP_RESPONSE_CODE_CREATED);
89
    }
90
91
    /**
92
     * PATCH relationships for specific entity id
93
     *
94
     * @param Request $request
95
     * @param int|string $id
96
     * @param string $relation
97
     * @return Response
98
     */
99
    public function updateRelations(Request $request, $id, string $relation) : Response
100
    {
101
        $model    = $this->presetRelations($request, $id, $relation);
102
        $resource = Json::getResource($this->formRequest, $model, $this->entity);
103
104
        return $this->getResponse(Json::prepareSerializedData($resource));
105
    }
106
107
    /**
108
     * @param Request $request
109
     * @param int|string $id
110
     * @param string $relation
111
     * @return mixed
112
     */
113
    private function presetRelations(Request $request, $id, string $relation)
114
    {
115
        $json = Json::decode($request->getContent());
116
        $this->setRelationships($json, $id, true);
117
118
        // set include for relations
119
        $_GET['include'] = $relation;
120
        $model           = $this->getEntity($id);
121
122
        if (empty($model)) {
123
            return $this->getResponse((new Json())->getErrors((new Errors())->getModelNotFound($this->entity, $id)),
124
                JSONApiInterface::HTTP_RESPONSE_CODE_NOT_FOUND);
125
        }
126
127
        return $model;
128
    }
129
130
    /**
131
     * DELETE relationships for specific entity id
132
     *
133
     * @param Request $request JSON API formatted string
134
     * @param int|string $id int id of an entity
135
     * @param string $relation
136
     * @return Response
137
     */
138
    public function deleteRelations(Request $request, $id, string $relation) : Response
139
    {
140
        $json        = Json::decode($request->getContent());
141
        $jsonApiRels = Json::getData($json);
142
        if (empty($jsonApiRels) === false) {
143
            $lowEntity = strtolower($this->entity);
144
            foreach ($jsonApiRels as $index => $val) {
145
                $rId = $val[ApiInterface::RAML_ID];
146
                // if pivot file exists then save
147
                $ucEntity = ucfirst($relation);
148
                $file     = DirsInterface::MODULES_DIR . PhpInterface::SLASH
149
                    . ConfigHelper::getModuleName() . PhpInterface::SLASH .
150
                    DirsInterface::ENTITIES_DIR . PhpInterface::SLASH .
151
                    $this->entity . $ucEntity . PhpInterface::PHP_EXT;
152
                if (file_exists(PhpInterface::SYSTEM_UPDIR . $file)) { // ManyToMany rel
153
                    $pivotEntity = Classes::getModelEntity($this->entity . $ucEntity);
154
                    // clean up old links
155
                    $this->getModelEntities(
156
                        $pivotEntity,
157
                        [
158
                            [
159
                                $lowEntity . PhpInterface::UNDERSCORE . ApiInterface::RAML_ID => $id,
160
                                $relation . PhpInterface::UNDERSCORE . ApiInterface::RAML_ID  => $rId,
161
                            ],
162
                        ]
163
                    )->delete();
164
                } else { // OneToOne/Many - note this is always updates one row related to entity e.g.:
165
                    // find article by id and update tag_id or topic_id
166
                    $entity = Classes::getModelEntity($this->entity);
167
168
                    /** @var \Illuminate\Database\Eloquent\Builder $model */
169
                    $model = $this->getModelEntities(
170
                        $entity, [
171
                            ApiInterface::RAML_ID, $id,
172
                        ]
173
                    );
174
175
                    $model->update([$relation . PhpInterface::UNDERSCORE . ApiInterface::RAML_ID => 0]);
176
                }
177
            }
178
        }
179
180
        return $this->getResponse(Json::prepareSerializedData(new Collection()), JSONApiInterface::HTTP_RESPONSE_CODE_NO_CONTENT);
181
    }
182
183
    /**
184
     * @param array $json
185
     * @param int|string $eId
186
     * @param bool $isRemovable
187
     */
188
    protected function setRelationships(array $json, $eId, bool $isRemovable = false) : void
189
    {
190
        $jsonApiRels = Json::getRelationships($json);
191
        if (empty($jsonApiRels) === false) {
192
            foreach ($jsonApiRels as $entity => $value) {
193
                if (empty($value[ApiInterface::RAML_DATA][ApiInterface::RAML_ID]) === false) {
194
                    // if there is only one relationship
195
                    $rId = $value[ApiInterface::RAML_DATA][ApiInterface::RAML_ID];
196
                    $this->saveRelationship($entity, $eId, $rId, $isRemovable);
197
                } else {
198
                    // if there is an array of relationships
199
                    foreach ($value[ApiInterface::RAML_DATA] as $index => $val) {
200
                        $rId = $val[ApiInterface::RAML_ID];
201
                        $this->saveRelationship($entity, $eId, $rId, $isRemovable);
202
                    }
203
                }
204
            }
205
        }
206
    }
207
208
    /**
209
     * @param      $entity
210
     * @param int|string $eId
211
     * @param int|string $rId
212
     * @param bool $isRemovable
213
     */
214
    private function saveRelationship($entity, $eId, $rId, bool $isRemovable = false) : void
215
    {
216
        $ucEntity  = Classes::getClassName($entity);
217
        $lowEntity = MigrationsHelper::getTableName($this->entity);
218
        // if pivot file exists then save
219
        $filePivot          = FileManager::getPivotFile($this->entity, $ucEntity);
220
        $filePivotInverse   = FileManager::getPivotFile($ucEntity, $this->entity);
221
        $pivotExists        = file_exists(PhpInterface::SYSTEM_UPDIR . $filePivot);
222
        $pivotInverseExists = file_exists(PhpInterface::SYSTEM_UPDIR . $filePivotInverse);
223
        if ($pivotExists === true || $pivotInverseExists === true) { // ManyToMany rel
224
            $pivotEntity = null;
225
226
            if ($pivotExists) {
227
                $pivotEntity = Classes::getModelEntity($this->entity . $ucEntity);
228
            } else {
229
                if ($pivotInverseExists) {
230
                    $pivotEntity = Classes::getModelEntity($ucEntity . $this->entity);
231
                }
232
            }
233
234
            if ($isRemovable === true) {
235
                $this->clearPivotBeforeSave($pivotEntity, $lowEntity, $eId);
0 ignored issues
show
Bug introduced by
It seems like $pivotEntity can also be of type null; however, parameter $pivotEntity of SoliDry\Extension\BaseRe...:clearPivotBeforeSave() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

235
                $this->clearPivotBeforeSave(/** @scrutinizer ignore-type */ $pivotEntity, $lowEntity, $eId);
Loading history...
236
            }
237
            $this->savePivot($pivotEntity, $lowEntity, $entity, $eId, $rId);
0 ignored issues
show
Bug introduced by
It seems like $pivotEntity can also be of type null; however, parameter $pivotEntity of SoliDry\Extension\BaseRelationsTrait::savePivot() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

237
            $this->savePivot(/** @scrutinizer ignore-type */ $pivotEntity, $lowEntity, $entity, $eId, $rId);
Loading history...
238
        } else { // OneToOne
239
            $this->saveModel($ucEntity, $lowEntity, $eId, $rId);
240
        }
241
    }
242
243
    /**
244
     * @param string $pivotEntity
245
     * @param string $lowEntity
246
     * @param int|string $eId
247
     */
248
    private function clearPivotBeforeSave(string $pivotEntity, string $lowEntity, $eId) : void
249
    {
250
        if ($this->relsRemoved === false) {
251
            // clean up old links
252
            $this->getModelEntities(
253
                $pivotEntity,
254
                [$lowEntity . PhpInterface::UNDERSCORE . ApiInterface::RAML_ID, $eId]
255
            )->delete();
256
            $this->relsRemoved = true;
0 ignored issues
show
Bug Best Practice introduced by
The property relsRemoved does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
257
        }
258
    }
259
260
    /**
261
     * @param string $pivotEntity
262
     * @param string $lowEntity
263
     * @param string $entity
264
     * @param int|string $eId
265
     * @param int|string $rId
266
     */
267
    private function savePivot(string $pivotEntity, string $lowEntity, string $entity, $eId, $rId) : void
268
    {
269
        $pivot                                                                  = new $pivotEntity();
270
        $pivot->{$entity . PhpInterface::UNDERSCORE . ApiInterface::RAML_ID}    = $rId;
271
        $pivot->{$lowEntity . PhpInterface::UNDERSCORE . ApiInterface::RAML_ID} = $eId;
272
        $pivot->save();
273
    }
274
275
    /**
276
     * Saves model with related id from linked table full duplex
277
     * @param string $ucEntity
278
     * @param string $lowEntity
279
     * @param int|string $eId
280
     * @param int|string $rId
281
     */
282
    private function saveModel(string $ucEntity, string $lowEntity, $eId, $rId) : void
283
    {
284
        $relEntity = Classes::getModelEntity($ucEntity);
285
        $model     = $this->getModelEntity($relEntity, $rId);
286
287
        // swap table and field trying to find rels with inverse
288
        if (!property_exists($model, $lowEntity . PhpInterface::UNDERSCORE . ApiInterface::RAML_ID)) {
289
            $ucTmp     = $ucEntity;
290
            $ucEntity  = ucfirst($lowEntity);
291
            $relEntity = Classes::getModelEntity($ucEntity);
292
            $model     = $this->getModelEntity($relEntity, $eId);
293
            $lowEntity = strtolower($ucTmp);
294
295
            $model->{$lowEntity . PhpInterface::UNDERSCORE . ApiInterface::RAML_ID} = $rId;
296
            $model->save();
297
            return;
298
        }
299
300
        $model->{$lowEntity . PhpInterface::UNDERSCORE . ApiInterface::RAML_ID} = $eId;
301
        $model->save();
302
    }
303
}