Completed
Push — master ( 829e7b...9495f2 )
by Nate
01:24
created

RelationshipManagerTrait::addOne()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 0
cts 17
cp 0
rs 9.6
c 0
b 0
f 0
cc 4
nc 5
nop 2
crap 20
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\managers;
10
11
use Craft;
12
use craft\base\ElementInterface;
13
use craft\helpers\ArrayHelper;
14
use Tightenco\Collect\Support\Collection;
15
use yii\base\Exception;
16
use yii\db\ActiveRecord;
17
use yii\db\QueryInterface;
18
19
/**
20
 * @author Flipbox Factory <[email protected]>
21
 * @since 2.0.0
22
 */
23
trait RelationshipManagerTrait
24
{
25
    use MutatedTrait;
26
27
    /**
28
     * @var Collection|null
29
     */
30
    protected $associations;
31
32
    /**
33
     * @param null $object
34
     * @return int|null
35
     */
36
    abstract protected function findKey($object = null);
37
38
    /**
39
     * @param array $criteria
40
     * @return QueryInterface
41
     */
42
    abstract protected function query(array $criteria = []): QueryInterface;
43
44
    /**
45
     * @param $object
46
     * @return ActiveRecord
47
     */
48
    abstract protected function create($object): ActiveRecord;
49
50
    /**
51
     *
52
     * @return ActiveRecord[][]
53
     */
54
    abstract protected function associationDelta(): array;
55
56
    /**
57
     * @return void
58
     */
59
    abstract protected function handleAssociationError();
60
61
    /**
62
     * @param ActiveRecord|ElementInterface|int|string $object
63
     * @return ActiveRecord
64
     */
65
    public function findOrCreate($object): ActiveRecord
66
    {
67
        if (null === ($association = $this->findOne($object))) {
68
            $association = $this->create($object);
69
        }
70
71
        return $association;
72
    }
73
74
    /**
75
     * @param ActiveRecord|ElementInterface|int|string $object
76
     * @return ActiveRecord
77
     * @throws Exception
78
     */
79
    public function findOrFail($object): ActiveRecord
80
    {
81
        if (null === ($association = $this->findOne($object))) {
82
            throw new Exception("Association could not be found.");
83
        }
84
85
        return $association;
86
    }
87
88
    /**
89
     * @return Collection
90
     */
91
    public function findAll(): Collection
92
    {
93
        if (null === $this->associations) {
94
            $this->setCache($this->query()->all(), false);
95
        }
96
97
        return $this->associations;
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->associations[$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
     * SET
125
     ************************************************************/
126
127
    /**
128
     * @param QueryInterface|ElementInterface[] $objects
129
     * @return static
130
     */
131
    public function setMany($objects)
132
    {
133
        if ($objects instanceof QueryInterface || $objects instanceof Collection) {
134
            $objects = $objects->all();
135
        }
136
137
        // Reset results
138
        $this->setCache([]);
139
140
        if (!empty($objects)) {
141
            if (!is_array($objects)) {
142
                $objects = [$objects];
143
            }
144
145
            $this->addMany($objects);
146
        }
147
148
        return $this;
149
    }
150
151
152
    /************************************************************
153
     * ADD
154
     ************************************************************/
155
156
    /**
157
     * @param QueryInterface|ElementInterface[] $objects
158
     * @return static
159
     */
160
    public function addMany($objects)
161
    {
162
        if ($objects instanceof QueryInterface || $objects instanceof Collection) {
163
            $objects = $objects->all();
164
        }
165
166
        if (!is_array($objects)) {
167
            $objects = [$objects];
168
        }
169
170
        // In case a config is directly passed
171
        if (ArrayHelper::isAssociative($objects)) {
172
            $objects = [$objects];
173
        }
174
175
        foreach ($objects as $object) {
176
            $this->addOne($object);
177
        }
178
179
        return $this;
180
    }
181
182
    /**
183
     * Associate a user to an organization
184
     *
185
     * @param ActiveRecord|ElementInterface|int|array $object
186
     * @param array $attributes
187
     * @return static
188
     */
189
    public function addOne($object, array $attributes = [])
190
    {
191
        if (empty($object)) {
192
            return $this;
193
        }
194
195
        if (null === ($association = $this->findOne($object))) {
196
            $association = $this->create($object);
197
            $this->addToCache($association);
198
        }
199
200
        if (!empty($attributes)) {
201
            Craft::configure(
202
                $association,
203
                $attributes
204
            );
205
        }
206
207
        return $this;
208
    }
209
210
211
    /************************************************************
212
     * REMOVE
213
     ************************************************************/
214
215
    /**
216
     * Dissociate an array of user associations from an organization
217
     *
218
     * @param QueryInterface|ElementInterface[] $objects
219
     * @return static
220
     */
221
    public function removeMany($objects)
222
    {
223
        if ($objects instanceof QueryInterface || $objects instanceof Collection) {
224
            $objects = $objects->all();
225
        }
226
227
        if (!is_array($objects)) {
228
            $objects = [$objects];
229
        }
230
231
        // In case a config is directly passed
232
        if (ArrayHelper::isAssociative($objects)) {
233
            $objects = [$objects];
234
        }
235
236
        foreach ($objects as $object) {
237
            $this->removeOne($object);
238
        }
239
240
        return $this;
241
    }
242
243
    /**
244
     * Dissociate a user from an organization
245
     *
246
     * @param ActiveRecord|ElementInterface|int|array
247
     * @return static
248
     */
249
    public function removeOne($object)
250
    {
251
        if (empty($object)) {
252
            return $this;
253
        }
254
255
        if (null !== ($key = $this->findKey($object))) {
256
            $this->removeFromCache($key);
257
        }
258
259
        return $this;
260
    }
261
262
    /**
263
     * Reset associations
264
     */
265
    public function reset()
266
    {
267
        $this->associations = null;
268
        $this->mutated = false;
269
        return $this;
270
    }
271
272
273
    /*******************************************
274
     * SAVE
275
     *******************************************/
276
277
    /**
278
     * @return bool
279
     */
280
    public function save(): bool
281
    {
282
        // No changes?
283
        if (!$this->isMutated()) {
284
            return true;
285
        }
286
287
        $success = true;
288
289
        list($newAssociations, $existingAssociations) = $this->associationDelta();
290
291
        // Delete those removed
292
        foreach ($existingAssociations as $existingAssociation) {
293
            if (!$existingAssociation->delete()) {
294
                $success = false;
295
            }
296
        }
297
298
        foreach ($newAssociations as $newAssociation) {
299
            if (!$newAssociation->save()) {
300
                $success = false;
301
            }
302
        }
303
304
        $this->setCache($newAssociations);
305
        $this->mutated = false;
306
307
        if (!$success) {
308
            $this->handleAssociationError();
309
        }
310
311
        return $success;
312
    }
313
314
    /*******************************************
315
     * ASSOCIATE
316
     *******************************************/
317
318
    /**
319
     * @param $object
320
     * @param array $attributes
321
     * @return bool
322
     */
323
    public function associateOne($object, array $attributes = []): bool
324
    {
325
        $association = $this->findOrCreate($object);
326
327
        if (!empty($attributes)) {
328
            Craft::configure(
329
                $association,
330
                $attributes
331
            );
332
        }
333
334
        if (!$association->save()) {
335
            $this->handleAssociationError();
336
            return false;
337
        }
338
339
        $this->reset();
340
341
        return true;
342
    }
343
344
    /**
345
     * @param QueryInterface|ElementInterface[] $objects
346
     * @return bool
347
     */
348
    public function associateMany($objects): bool
349
    {
350
        if ($objects instanceof QueryInterface || $objects instanceof Collection) {
351
            $objects = $objects->all();
352
        }
353
354
        if (empty($objects)) {
355
            return true;
356
        }
357
358
        $this->addMany($objects);
359
360
        return $this->save();
361
    }
362
363
364
    /*******************************************
365
     * DISSOCIATE
366
     *******************************************/
367
368
    /**
369
     * @noinspection PhpDocMissingThrowsInspection
370
     *
371
     * @param ActiveRecord|ElementInterface|int|array $object
372
     * @return bool
373
     */
374
    public function dissociateOne($object): bool
375
    {
376
        if (null === ($association = $this->findOne($object))) {
377
            return true;
378
        }
379
380
        /** @noinspection PhpUnhandledExceptionInspection */
381
        if (!$association->delete()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $association->delete() of type false|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
382
            $this->handleAssociationError();
383
            return false;
384
        }
385
386
        $this->removeOne($association);
387
388
        return true;
389
    }
390
391
    /**
392
     * @param QueryInterface|ElementInterface[] $objects
393
     * @return bool
394
     */
395
    public function dissociateMany($objects): bool
396
    {
397
        if ($objects instanceof QueryInterface || $objects instanceof Collection) {
398
            $objects = $objects->all();
399
        }
400
401
        if (empty($objects)) {
402
            return true;
403
        }
404
405
        $this->removeMany($objects);
406
407
        return $this->save();
408
    }
409
410
411
    /*******************************************
412
     * CACHE
413
     *******************************************/
414
415
    /**
416
     * @param array $associations
417
     * @param bool $mutated
418
     * @return static
419
     */
420
    protected function setCache(array $associations, bool $mutated = true): self
421
    {
422
        $this->associations = Collection::make($associations);
423
        $this->mutated = $mutated;
424
425
        return $this;
426
    }
427
428
    /**
429
     * @param $association
430
     * @return RelationshipManagerTrait
0 ignored issues
show
Comprehensibility Bug introduced by
The return type RelationshipManagerTrait 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...
431
     */
432
    protected function addToCache($association): self
433
    {
434
        if (null === $this->associations) {
435
            return $this->setCache([$association], true);
436
        }
437
438
        $this->associations->push($association);
439
        $this->mutated = true;
440
441
        return $this;
442
    }
443
444
    /**
445
     * @param int $key
446
     * @return RelationshipManagerTrait
0 ignored issues
show
Comprehensibility Bug introduced by
The return type RelationshipManagerTrait 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...
447
     */
448
    protected function removeFromCache(int $key): self
449
    {
450
        $this->associations->forget($key);
451
        $this->mutated = true;
452
453
        return $this;
454
    }
455
}
456