Completed
Push — develop ( bcbf28...a87a8b )
by Nate
03:33
created

RelationshipTrait::insertCollection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
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->relations->get($key);
111
    }
112
113
    /**
114
     * @param ActiveRecord|ElementInterface|int|string $object
115
     * @return bool
116
     */
117
    public function exists($object): bool
118
    {
119
        return null !== $this->findKey($object);
120
    }
121
122
123
    /************************************************************
124
     * COLLECTIONS
125
     ************************************************************/
126
127
    /**
128
     * @inheritDoc
129
     */
130
    public function getRelationships(): Collection
131
    {
132
        if (null === $this->relations) {
133
            $this->relations = $this->existingRelationships();
134
        }
135
136
        return $this->relations;
137
    }
138
139
    /************************************************************
140
     * ADD / REMOVE
141
     ************************************************************/
142
143
    /**
144
     * Add one or many object relations (but do not save)
145
     *
146
     * @param $objects
147
     * @param array $attributes
148
     * @return RelationshipInterface
149
     */
150
    public function add($objects, array $attributes = []): RelationshipInterface
151
    {
152
        foreach ($this->objectArray($objects) as $object) {
153
            $this->addOne($object, $attributes);
154
        }
155
156
        return $this;
157
    }
158
159
    /**
160
     * @param $object
161
     * @param array $attributes
162
     * @return RelationshipInterface
163
     */
164
    protected function addOne($object, array $attributes = []): RelationshipInterface
165
    {
166
        $isNew = false;
167
168
        // Check if it's already linked
169
        if (null === ($association = $this->findOne($object))) {
170
            $association = $this->create($object);
171
            $isNew = true;
172
        }
173
174
        // Modify?
175
        if (!empty($attributes)) {
176
            Craft::configure(
177
                $association,
178
                $attributes
179
            );
180
181
            $this->mutated = true;
182
183
            if (!$isNew) {
184
                $this->updateCollection($this->relations, $association);
185
            }
186
        }
187
188
        if ($isNew) {
189
            $this->addToRelations($association);
190
            $this->mutated = true;
191
        }
192
193
        return $this;
194
    }
195
196
    /**
197
     * @param $objects
198
     * @return RelationshipInterface
199
     */
200
    public function remove($objects): RelationshipInterface
201
    {
202
        foreach ($this->objectArray($objects) as $object) {
203
            if (null !== ($key = $this->findKey($object))) {
204
                $this->removeFromRelations($key);
205
            }
206
        }
207
208
        return $this;
209
    }
210
211
212
    /************************************************************
213
     * RESET
214
     ************************************************************/
215
216
    /**
217
     * Reset associations
218
     * @return RelationshipInterface
219
     */
220
    public function reset(): RelationshipInterface
221
    {
222
        $this->relations = null;
223
        $this->mutated = false;
224
        return $this;
225
    }
226
227
    /**
228
     * Reset associations
229
     * @return RelationshipInterface
230
     */
231
    public function clear(): RelationshipInterface
232
    {
233
        $this->newRelations([]);
234
        return $this;
235
    }
236
237
238
    /*******************************************
239
     * SAVE
240
     *******************************************/
241
242
    /**
243
     * @return bool
244
     */
245
    public function save(): bool
246
    {
247
        // No changes?
248
        if (!$this->isMutated()) {
249
            return true;
250
        }
251
252
        $success = true;
253
254
        list($save, $delete) = $this->delta();
255
256
        foreach ($delete as $relationship) {
257
            if (!$relationship->delete()) {
258
                $success = false;
259
            }
260
        }
261
262
        foreach ($save as $relationship) {
263
            if (!$relationship->save()) {
264
                $success = false;
265
            }
266
        }
267
268
        $this->newRelations($save, false);
269
270
        return $success;
271
    }
272
273
274
    /*******************************************
275
     * CACHE
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
        if (null === $this->relations) {
298
            return $this->newRelations([$association], true);
299
        }
300
301
        $this->insertCollection($this->relations, $association);
302
        $this->mutated = true;
303
304
        return $this;
305
    }
306
307
    /**
308
     * @param int $key
309
     * @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...
310
     */
311
    protected function removeFromRelations(int $key): self
312
    {
313
        $this->relations->forget($key);
314
        $this->mutated = true;
315
316
        return $this;
317
    }
318
319
    /**
320
     * @param array $associations
321
     * @return Collection
322
     */
323
    protected function createRelations(array $associations = []): Collection
324
    {
325
        $collection = new Collection();
326
        foreach ($associations as $association) {
327
            $this->insertCollection($collection, $association);
328
        }
329
330
        return $collection;
331
    }
332
333
    /**
334
     * Update a relation that's already association.
335
     *
336
     * @param Collection $collection
337
     * @param $association
338
     */
339
    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...
340
    {
341
    }
342
343
    /**
344
     * Insert a relation that's not already associated
345
     *
346
     * @param Collection $collection
347
     * @param $association
348
     */
349
    protected function insertCollection(Collection $collection, $association)
350
    {
351
        $collection->push($association);
352
    }
353
354
355
    /*******************************************
356
     * UTILITIES
357
     *******************************************/
358
359
    /**
360
     * @inheritDoc
361
     */
362
    protected function resolveObject($object = null)
363
    {
364
        if (null === $object) {
365
            return null;
366
        }
367
368
        if (is_array($object) &&
369
            null !== ($id = ArrayHelper::getValue($object, 'id'))
370
        ) {
371
            $object = ['id' => $id];
372
        }
373
374
        return $this->resolveObjectInternal($object);
375
    }
376
377
    /**
378
     * Ensure we're working with an array of objects, not configs, etc
379
     *
380
     * @param array|QueryInterface|Collection|ElementInterface|UserAssociation $objects
381
     * @return array
382
     */
383
    protected function objectArray($objects): array
384
    {
385
        if ($objects instanceof QueryInterface || $objects instanceof Collection) {
386
            $objects = $objects->all();
387
        }
388
389
        // proper array
390
        if (!is_array($objects) || ArrayHelper::isAssociative($objects)) {
391
            $objects = [$objects];
392
        }
393
394
        return array_filter($objects);
395
    }
396
}
397