RelationshipTrait::save()   B
last analyzed

Complexity

Conditions 6
Paths 10

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 0
cts 20
cp 0
rs 8.8657
c 0
b 0
f 0
cc 6
nc 10
nop 0
crap 42
1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://flipboxfactory.com/software/organization/license
6
 * @link       https://www.flipboxfactory.com/software/organization/
7
 */
8
9
namespace flipbox\organizations\relationships;
10
11
use Craft;
12
use craft\base\ElementInterface;
13
use craft\helpers\ArrayHelper;
14
use flipbox\organizations\records\UserAssociation;
15
use Tightenco\Collect\Support\Collection;
16
use yii\base\Exception;
17
use yii\db\ActiveRecord;
18
use yii\db\QueryInterface;
19
20
/**
21
 * @author Flipbox Factory <[email protected]>
22
 * @since 2.0.0
23
 *
24
 * @mixin RelationshipInterface
25
 */
26
trait RelationshipTrait
27
{
28
    use MutatedTrait;
29
30
    /**
31
     * @var Collection|null
32
     */
33
    protected $relations;
34
35
    /**
36
     * @param null $object
37
     * @return int|null
38
     */
39
    abstract protected function findKey($object = null);
40
41
    /**
42
     * Create a new association/relationship record
43
     *
44
     * @param $object
45
     * @return ActiveRecord
46
     */
47
    abstract protected function create($object): ActiveRecord;
48
49
    /**
50
     * Resolve the object that will be related
51
     *
52
     * @param $object
53
     * @return mixed
54
     */
55
    abstract protected function resolveObjectInternal($object);
56
57
    /**
58
     * A collection of existing relationships, indexed by id.  We'll compare these with
59
     * and new relations to determine if we need to add/remove relations
60
     *
61
     * @return Collection
62
     */
63
    abstract protected function existingRelationships(): Collection;
64
65
    /**
66
     * An array of relationships to save and an array of relationships to delete.  This will
67
     * want to be used as such: `list($save, $delete) = $this->delta();`
68
     *
69
     * @return array
70
     */
71
    abstract protected function delta(): array;
72
73
    /**
74
     * @param ActiveRecord|ElementInterface|int|string $object
75
     * @return ActiveRecord
76
     */
77
    public function findOrCreate($object): ActiveRecord
78
    {
79
        if (null === ($association = $this->findOne($object))) {
80
            $association = $this->create($object);
81
        }
82
83
        return $association;
84
    }
85
86
    /**
87
     * @param ActiveRecord|ElementInterface|int|string $object
88
     * @return ActiveRecord
89
     * @throws Exception
90
     */
91
    public function findOrFail($object): ActiveRecord
92
    {
93
        if (null === ($association = $this->findOne($object))) {
94
            throw new Exception("Association could not be found.");
95
        }
96
97
        return $association;
98
    }
99
100
    /**
101
     * @param ActiveRecord|ElementInterface|int|string|null $object
102
     * @return ActiveRecord|null
103
     */
104
    public function findOne($object = null)
105
    {
106
        if (null === ($key = $this->findKey($object))) {
107
            return null;
108
        }
109
110
        return $this->getRelationships()->get($key);
111
    }
112
113
114
    /************************************************************
115
     * COLLECTIONS
116
     ************************************************************/
117
118
    /**
119
     * @inheritDoc
120
     */
121
    public function getRelationships(): Collection
122
    {
123
        if (null === $this->relations) {
124
            $this->relations = $this->existingRelationships();
125
        }
126
127
        return $this->relations;
128
    }
129
130
131
    /************************************************************
132
     * ADD / REMOVE
133
     ************************************************************/
134
135
    /**
136
     * Add one or many object relations (but do not save)
137
     *
138
     * @param $objects
139
     * @param array $attributes
140
     * @return RelationshipInterface
141
     */
142
    public function add($objects, array $attributes = []): RelationshipInterface
143
    {
144
        foreach ($this->objectArray($objects) as $object) {
145
            $this->addOne($object, $attributes);
146
        }
147
148
        return $this;
149
    }
150
151
    /**
152
     * @param $object
153
     * @param array $attributes
154
     * @return RelationshipInterface
155
     */
156
    private function addOne($object, array $attributes = []): RelationshipInterface
157
    {
158
        $isNew = false;
159
160
        // Check if it's already linked
161
        if (null === ($association = $this->findOne($object))) {
162
            $association = $this->create($object);
163
            $isNew = true;
164
        }
165
166
        // Modify?
167
        if (!empty($attributes)) {
168
            Craft::configure(
169
                $association,
170
                $attributes
171
            );
172
173
            $this->mutated = true;
174
175
            if (!$isNew) {
176
                $this->updateCollection($this->relations, $association);
177
            }
178
        }
179
180
        if ($isNew) {
181
            $this->addToRelations($association);
182
        }
183
184
        return $this;
185
    }
186
187
    /**
188
     * @param $objects
189
     * @return RelationshipInterface
190
     */
191
    public function remove($objects): RelationshipInterface
192
    {
193
        foreach ($this->objectArray($objects) as $object) {
194
            if (null !== ($key = $this->findKey($object))) {
195
                $this->removeFromRelations($key);
196
            }
197
        }
198
199
        return $this;
200
    }
201
202
203
    /*******************************************
204
     * SAVE
205
     *******************************************/
206
207
    /**
208
     * @return bool
209
     */
210
    public function save(): bool
211
    {
212
        // No changes?
213
        if (!$this->isMutated()) {
214
            return true;
215
        }
216
217
        $success = true;
218
219
        list($save, $delete) = $this->delta();
220
221
        foreach ($delete as $relationship) {
222
            if (!$relationship->delete()) {
223
                $success = false;
224
            }
225
        }
226
227
        foreach ($save as $relationship) {
228
            if (!$relationship->save()) {
229
                $success = false;
230
            }
231
        }
232
233
        $this->reset();
234
235
        return $success;
236
    }
237
238
239
    /************************************************************
240
     * UTILITIES
241
     ************************************************************/
242
243
    /**
244
     * Reset associations
245
     * @return RelationshipInterface
246
     */
247
    public function reset(): RelationshipInterface
248
    {
249
        $this->relations = null;
250
        $this->mutated = false;
251
        return $this;
252
    }
253
254
    /**
255
     * Reset associations
256
     * @return RelationshipInterface
257
     */
258
    public function clear(): RelationshipInterface
259
    {
260
        $this->newRelations([]);
261
        return $this;
262
    }
263
264
    /**
265
     * @param ActiveRecord|ElementInterface|int|string $object
266
     * @return bool
267
     */
268
    public function exists($object): bool
269
    {
270
        return null !== $this->findKey($object);
271
    }
272
273
274
    /*******************************************
275
     * COLLECTION UTILS
276
     *******************************************/
277
278
    /**
279
     * @param array $associations
280
     * @param bool $mutated
281
     * @return static
282
     */
283
    protected function newRelations(array $associations, bool $mutated = true): self
284
    {
285
        $this->relations = $this->createRelations($associations);
286
        $this->mutated = $mutated;
287
288
        return $this;
289
    }
290
291
    /**
292
     * @param $association
293
     * @return RelationshipTrait
0 ignored issues
show
Comprehensibility Bug introduced by
The return type RelationshipTrait is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?

In PHP traits cannot be used for type-hinting as they do not define a well-defined structure. This is because any class that uses a trait can rename that trait’s methods.

If you would like to return an object that has a guaranteed set of methods, you could create a companion interface that lists these methods explicitly.

Loading history...
294
     */
295
    protected function addToRelations($association): self
296
    {
297
        $this->insertCollection($this->getRelationships(), $association);
298
        $this->mutated = true;
299
300
        return $this;
301
    }
302
303
    /**
304
     * @param int $key
305
     * @return RelationshipTrait
0 ignored issues
show
Comprehensibility Bug introduced by
The return type RelationshipTrait is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?

In PHP traits cannot be used for type-hinting as they do not define a well-defined structure. This is because any class that uses a trait can rename that trait’s methods.

If you would like to return an object that has a guaranteed set of methods, you could create a companion interface that lists these methods explicitly.

Loading history...
306
     */
307
    protected function removeFromRelations(int $key): self
308
    {
309
        $this->relations->forget($key);
310
        $this->mutated = true;
311
312
        return $this;
313
    }
314
315
    /**
316
     * @param array $associations
317
     * @return Collection
318
     */
319
    protected function createRelations(array $associations = []): Collection
320
    {
321
        $collection = new Collection();
322
        foreach ($associations as $association) {
323
            $this->insertCollection($collection, $association);
324
        }
325
326
        return $collection;
327
    }
328
329
    /**
330
     * Update a relation that's already association.
331
     *
332
     * @param Collection $collection
333
     * @param $association
334
     */
335
    protected function updateCollection(Collection $collection, $association)
0 ignored issues
show
Unused Code introduced by
The parameter $collection 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...
Unused Code introduced by
The parameter $association 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...
336
    {
337
    }
338
339
    /**
340
     * Insert a relation that's not already associated
341
     *
342
     * @param Collection $collection
343
     * @param $association
344
     */
345
    protected function insertCollection(Collection $collection, $association)
346
    {
347
        $collection->push($association);
348
    }
349
350
351
    /*******************************************
352
     * RESOLVERS
353
     *******************************************/
354
355
    /**
356
     * @inheritDoc
357
     */
358
    protected function resolveObject($object = null)
359
    {
360
        if (null === $object) {
361
            return null;
362
        }
363
364
        if (is_array($object) &&
365
            null !== ($id = ArrayHelper::getValue($object, 'id'))
366
        ) {
367
            $object = ['id' => $id];
368
        }
369
370
        return $this->resolveObjectInternal($object);
371
    }
372
373
    /**
374
     * Ensure we're working with an array of objects, not configs, etc
375
     *
376
     * @param array|QueryInterface|Collection|ElementInterface|UserAssociation $objects
377
     * @return array
378
     */
379
    protected function objectArray($objects): array
380
    {
381
        if ($objects instanceof QueryInterface || $objects instanceof Collection) {
382
            $objects = $objects->all();
383
        }
384
385
        // proper array
386
        if (!is_array($objects) || ArrayHelper::isAssociative($objects)) {
387
            $objects = [$objects];
388
        }
389
390
        return array_filter($objects);
391
    }
392
}
393