Completed
Push — develop ( 485964...fec580 )
by Nate
04:06
created

AssociationManagerTrait::findOrFail()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 7
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6
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 yii\base\Exception;
15
use yii\db\ActiveRecord;
16
use yii\db\QueryInterface;
17
18
/**
19
 * @author Flipbox Factory <[email protected]>
20
 * @since 1.1.0
21
 */
22
trait AssociationManagerTrait
23
{
24
    use MutatedTrait;
25
26
    /**
27
     * @var ActiveRecord[]|null
28
     */
29
    protected $associations;
30
31
    /**
32
     * @param null $object
33
     * @return int|null
34
     */
35
    abstract protected function findKey($object = null);
36
37
    /**
38
     * @param array $criteria
39
     * @return QueryInterface
40
     */
41
    abstract public function query(array $criteria = []): QueryInterface;
42
43
    /**
44
     * @param $object
45
     * @return ActiveRecord
46
     */
47
    abstract public function create($object): ActiveRecord;
48
49
    /**
50
     *
51
     * @return ActiveRecord[][]
52
     */
53
    abstract protected function associationDelta(): array;
54
55
    /**
56
     * @return void
57
     */
58
    abstract protected function handleAssociationError();
59
60
    /**
61
     * @param ActiveRecord|ElementInterface|int|string $object
62
     * @return ActiveRecord
63
     */
64
    public function findOrCreate($object): ActiveRecord
65
    {
66
        if (null === ($association = $this->findOne($object))) {
67
            $association = $this->create($object);
68
        }
69
70
        return $association;
71
    }
72
73
    /**
74
     * @param ActiveRecord|ElementInterface|int|string $object
75
     * @return ActiveRecord
76
     * @throws Exception
77
     */
78
    public function findOrFail($object): ActiveRecord
79
    {
80
        if (null === ($association = $this->findOne($object))) {
81
            throw new Exception("Association could not be found.");
82
        }
83
84
        return $association;
85
    }
86
87
    /**
88
     * @return ActiveRecord[]
89
     */
90
    public function findAll(): array
91
    {
92
        if (null === $this->associations) {
93
            $this->setCache($this->query()->all());
94
        }
95
96
        return $this->associations;
97
    }
98
99
    /**
100
     * @param ActiveRecord|ElementInterface|int|string|null $object
101
     * @return ActiveRecord|null
102
     */
103
    public function findOne($object = null)
104
    {
105
        if (null === ($key = $this->findKey($object))) {
106
            return null;
107
        }
108
109
        return $this->associations[$key];
110
    }
111
112
    /**
113
     * @param ActiveRecord|ElementInterface|int|string $object
114
     * @return bool
115
     */
116
    public function exists($object): bool
117
    {
118
        return null !== $this->findKey($object);
119
    }
120
121
122
    /************************************************************
123
     * SET
124
     ************************************************************/
125
126
    /**
127
     * @param QueryInterface|ElementInterface[] $objects
128
     * @return $this
129
     */
130
    public function setMany($objects)
131
    {
132
        if ($objects instanceof QueryInterface) {
133
            $objects = $objects->all();
134
        }
135
136
        // Reset results
137
        $this->setCache([]);
138
139
        if (!empty($objects)) {
140
            if (!is_array($objects)) {
141
                $objects = [$objects];
142
            }
143
144
            $this->addMany($objects);
145
        }
146
147
        return $this;
148
    }
149
150
151
    /************************************************************
152
     * ADD
153
     ************************************************************/
154
155
    /**
156
     * @param QueryInterface|ElementInterface[] $objects
157
     * @return $this
158
     */
159
    public function addMany($objects)
160
    {
161
        if ($objects instanceof QueryInterface) {
162
            $objects = $objects->all();
163
        }
164
165
        if (!is_array($objects)) {
166
            $objects = [$objects];
167
        }
168
169
        // In case a config is directly passed
170
        if (ArrayHelper::isAssociative($objects)) {
171
            $objects = [$objects];
172
        }
173
174
        foreach ($objects as $object) {
175
            $this->addOne($object);
176
        }
177
178
        return $this;
179
    }
180
181
    /**
182
     * Associate a user to an organization
183
     *
184
     * @param ActiveRecord|ElementInterface|int|array $object
185
     * @param array $attributes
186
     * @return $this
187
     */
188
    public function addOne($object, array $attributes = [])
189
    {
190
        if (null === ($association = $this->findOne($object))) {
191
            $association = $this->create($object);
192
            $this->addToCache($association);
193
        }
194
195
        if (!empty($attributes)) {
196
            Craft::configure(
197
                $association,
198
                $attributes
199
            );
200
        }
201
202
        return $this;
203
    }
204
205
206
    /************************************************************
207
     * REMOVE
208
     ************************************************************/
209
210
    /**
211
     * Dissociate an array of user associations from an organization
212
     *
213
     * @param QueryInterface|ElementInterface[] $objects
214
     * @return $this
215
     */
216
    public function removeMany($objects)
217
    {
218
        if ($objects instanceof QueryInterface) {
219
            $objects = $objects->all();
220
        }
221
222
        if (!is_array($objects)) {
223
            $objects = [$objects];
224
        }
225
226
        // In case a config is directly passed
227
        if (ArrayHelper::isAssociative($objects)) {
228
            $objects = [$objects];
229
        }
230
231
        foreach ($objects as $object) {
232
            $this->removeOne($object);
233
        }
234
235
        return $this;
236
    }
237
238
    /**
239
     * Dissociate a user from an organization
240
     *
241
     * @param ActiveRecord|ElementInterface|int|array
242
     * @return $this
243
     */
244
    public function removeOne($object)
245
    {
246
        if (null !== ($key = $this->findKey($object))) {
247
            $this->removeFromCache($key);
248
        }
249
250
        return $this;
251
    }
252
253
    /**
254
     * Reset associations
255
     */
256
    public function reset()
257
    {
258
        $this->associations = null;
259
        $this->mutated = false;
260
        return $this;
261
    }
262
263
264
    /*******************************************
265
     * SAVE
266
     *******************************************/
267
268
    /**
269
     * @return bool
270
     */
271
    public function save(): bool
272
    {
273
        // No changes?
274
        if (!$this->isMutated()) {
275
            return true;
276
        }
277
278
        $success = true;
279
280
        list($newAssociations, $existingAssociations) = $this->associationDelta();
281
282
        // Delete those removed
283
        foreach ($existingAssociations as $existingAssociation) {
284
            if (!$existingAssociation->delete()) {
285
                $success = false;
286
            }
287
        }
288
289
        foreach ($newAssociations as $newAssociation) {
290
            if (!$newAssociation->save()) {
291
                $success = false;
292
            }
293
        }
294
295
        $this->setCache($newAssociations);
296
        $this->mutated = false;
297
298
        if (!$success) {
299
            $this->handleAssociationError();
300
        }
301
302
        return $success;
303
    }
304
305
    /*******************************************
306
     * ASSOCIATE
307
     *******************************************/
308
309
    /**
310
     * @param $object
311
     * @param array $attributes
312
     * @return bool
313
     */
314
    public function associateOne($object, array $attributes = []): bool
315
    {
316
        $association = $this->findOrCreate($object);
317
318
        if (!empty($attributes)) {
319
            Craft::configure(
320
                $association,
321
                $attributes
322
            );
323
        }
324
325
        if (!$association->save()) {
326
            $this->handleAssociationError();
327
            return false;
328
        }
329
330
        $this->reset();
331
332
        return true;
333
    }
334
335
    /**
336
     * @param QueryInterface|ElementInterface[] $objects
337
     * @return bool
338
     */
339
    public function associateMany($objects): bool
340
    {
341
        if ($objects instanceof QueryInterface) {
342
            $objects = $objects->all();
343
        }
344
345
        if (empty($objects)) {
346
            return true;
347
        }
348
349
        $this->addMany($objects);
350
351
        return $this->save();
352
    }
353
354
355
    /*******************************************
356
     * DISSOCIATE
357
     *******************************************/
358
359
    /**
360
     * @noinspection PhpDocMissingThrowsInspection
361
     *
362
     * @param ActiveRecord|ElementInterface|int|array $object
363
     * @return bool
364
     */
365
    public function dissociateOne($object): bool
366
    {
367
        if (null === ($association = $this->findOne($object))) {
368
            return true;
369
        }
370
371
        /** @noinspection PhpUnhandledExceptionInspection */
372
        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...
373
            $this->handleAssociationError();
374
            return false;
375
        }
376
377
        $this->removeOne($association);
378
379
        return true;
380
    }
381
382
    /**
383
     * @param QueryInterface|ElementInterface[] $objects
384
     * @return bool
385
     */
386
    public function dissociateMany($objects): bool
387
    {
388
        if ($objects instanceof QueryInterface) {
389
            $objects = $objects->all();
390
        }
391
392
        if (empty($objects)) {
393
            return true;
394
        }
395
396
        $this->removeMany($objects);
397
398
        return $this->save();
399
    }
400
401
402
    /*******************************************
403
     * CACHE
404
     *******************************************/
405
406
    /**
407
     * @param array $associations
408
     * @return static
409
     */
410
    protected function setCache(array $associations): self
411
    {
412
        $this->associations = $associations;
413
        $this->mutated = true;
414
415
        return $this;
416
    }
417
418
    /**
419
     * @param $association
420
     * @return AssociationManagerTrait
0 ignored issues
show
Comprehensibility Bug introduced by
The return type AssociationManagerTrait 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...
421
     */
422
    protected function addToCache($association): self
423
    {
424
        $this->associations[] = $association;
425
        $this->mutated = true;
426
427
        return $this;
428
    }
429
430
    /**
431
     * @param int $key
432
     * @return AssociationManagerTrait
0 ignored issues
show
Comprehensibility Bug introduced by
The return type AssociationManagerTrait 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...
433
     */
434
    protected function removeFromCache(int $key): self
435
    {
436
        unset($this->associations[$key]);
437
        $this->mutated = true;
438
439
        return $this;
440
    }
441
}
442