Completed
Push — develop ( eed049...0ceb6c )
by Nate
01:42
created

RelationshipTrait::objectArray()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 0
cts 10
cp 0
rs 9.5222
c 0
b 0
f 0
cc 5
nc 4
nop 1
crap 30
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 $collection;
34
35
    /**
36
     * @param null $object
37
     * @return int|null
38
     */
39
    abstract protected function findKey($object = null);
40
41
    /**
42
     * @param array $criteria
43
     * @return QueryInterface
44
     */
45
    abstract protected function query(array $criteria = []): QueryInterface;
46
47
    /**
48
     * @param $object
49
     * @return ActiveRecord
50
     */
51
    abstract protected function create($object): ActiveRecord;
52
53
    /**
54
     * @return ActiveRecord[][]
55
     */
56
    abstract protected function associationDelta(): array;
57
58
    /**
59
     * @return void
60
     */
61
    abstract protected function handleAssociationError();
62
63
    /**
64
     * @param ActiveRecord|ElementInterface|int|string $object
65
     * @return ActiveRecord
66
     */
67
    public function findOrCreate($object): ActiveRecord
68
    {
69
        if (null === ($association = $this->findOne($object))) {
70
            $association = $this->create($object);
71
        }
72
73
        return $association;
74
    }
75
76
    /**
77
     * @param ActiveRecord|ElementInterface|int|string $object
78
     * @return ActiveRecord
79
     * @throws Exception
80
     */
81
    public function findOrFail($object): ActiveRecord
82
    {
83
        if (null === ($association = $this->findOne($object))) {
84
            throw new Exception("Association could not be found.");
85
        }
86
87
        return $association;
88
    }
89
90
    /**
91
     * @return Collection
92
     *
93
     * @deprecated use `getCollection()`
94
     */
95
    public function findAll(): Collection
96
    {
97
        return $this->getRelationships();
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->collection->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->collection) {
133
            $this->collection = new Collection(
134
                $this->query()->all()
135
            );
136
        }
137
138
        return $this->collection;
139
    }
140
141
142
    /************************************************************
143
     * SET
144
     ************************************************************/
145
146
    /**
147
     * @param QueryInterface|ElementInterface[] $objects
148
     * @param array $attributes
149
     * @return RelationshipInterface
150
     *
151
     * @deprecated use `reset()->add($objects, $attributes)`
152
     */
153
    public function setMany($objects, array $attributes = []): RelationshipInterface
154
    {
155
        return $this->clear()
156
            ->add($objects, $attributes);
157
    }
158
159
160
    /************************************************************
161
     * ADD
162
     ************************************************************/
163
164
    /**
165
     * @param QueryInterface|ElementInterface[] $objects
166
     * @param array $attributes
167
     * @return RelationshipInterface
168
     *
169
     * @deprecated use `add($objects, $attributes)`
170
     */
171
    public function addMany($objects, array $attributes = []): RelationshipInterface
172
    {
173
        return $this->add($objects, $attributes);
174
    }
175
176
    /**
177
     * Associate a user to an organization
178
     *
179
     * @param ActiveRecord|ElementInterface|int|array $object
180
     * @param array $attributes
181
     * @return RelationshipInterface
182
     *
183
     * @deprecated use `add($objects, $attributes)`
184
     */
185
    public function addOne($object, array $attributes = []): RelationshipInterface
186
    {
187
        return $this->add($object, $attributes);
188
    }
189
190
    /**
191
     * Add one or many object relations (but do not save)
192
     *
193
     * @param $objects
194
     * @param array $attributes
195
     * @return RelationshipInterface
196
     */
197
    public function add($objects, array $attributes = []): RelationshipInterface
198
    {
199
        foreach ($this->objectArray($objects) as $object) {
200
            if (null === ($association = $this->findOne($object))) {
201
                $association = $this->create($object);
202
                $this->addToCollection($association);
203
            }
204
205
            if (!empty($attributes)) {
206
                Craft::configure(
207
                    $association,
208
                    $attributes
209
                );
210
211
                $this->mutated = true;
212
            }
213
        }
214
215
        return $this;
216
    }
217
218
219
    /************************************************************
220
     * REMOVE
221
     ************************************************************/
222
223
    /**
224
     * Dissociate an array of user associations from an organization
225
     *
226
     * @param QueryInterface|ElementInterface[] $objects
227
     * @return RelationshipInterface
228
     *
229
     * @deprecated use `remove($objects)`
230
     */
231
    public function removeMany($objects): RelationshipInterface
232
    {
233
        return $this->remove($objects);
234
    }
235
236
    /**
237
     * Dissociate a user from an organization
238
     *
239
     * @param ActiveRecord|ElementInterface|int|array
240
     * @return RelationshipInterface
241
     *
242
     * @deprecated use `remove($objects)`
243
     */
244
    public function removeOne($object): RelationshipInterface
245
    {
246
        return $this->remove($object);
247
    }
248
249
    /**
250
     * @param $objects
251
     * @return RelationshipInterface
252
     */
253
    public function remove($objects): RelationshipInterface
254
    {
255
        foreach ($this->objectArray($objects) as $object) {
256
            if (null !== ($key = $this->findKey($object))) {
257
                $this->removeFromCollection($key);
258
            }
259
        }
260
261
        return $this;
262
    }
263
264
265
    /************************************************************
266
     * RESET
267
     ************************************************************/
268
269
    /**
270
     * Reset associations
271
     * @return RelationshipInterface
272
     */
273
    public function reset(): RelationshipInterface
274
    {
275
        $this->collection = null;
276
        $this->mutated = false;
277
        return $this;
278
    }
279
280
    /**
281
     * Reset associations
282
     * @return RelationshipInterface
283
     */
284
    public function clear(): RelationshipInterface
285
    {
286
        $this->newCollection([]);
287
        return $this;
288
    }
289
290
291
    /*******************************************
292
     * SAVE
293
     *******************************************/
294
295
    /**
296
     * @return bool
297
     */
298
    public function save(): bool
299
    {
300
        // No changes?
301
        if (!$this->isMutated()) {
302
            return true;
303
        }
304
305
        $success = true;
306
307
        list($newAssociations, $existingAssociations) = $this->associationDelta();
308
309
        // Delete those removed
310
        foreach ($existingAssociations as $existingAssociation) {
311
            if (!$existingAssociation->delete()) {
312
                $success = false;
313
            }
314
        }
315
316
        foreach ($newAssociations as $newAssociation) {
317
            if (!$newAssociation->save()) {
318
                $success = false;
319
            }
320
        }
321
322
        $this->newCollection($newAssociations);
323
        $this->mutated = false;
324
325
        if (!$success) {
326
            $this->handleAssociationError();
327
        }
328
329
        return $success;
330
    }
331
332
333
    /*******************************************
334
     * ASSOCIATE
335
     *******************************************/
336
337
    /**
338
     * @param $object
339
     * @param array $attributes
340
     * @return bool
341
     *
342
     * @deprecated use `add($object, $attributes)->save()`
343
     */
344
    public function associateOne($object, array $attributes = []): bool
345
    {
346
        return $this->add($object, $attributes)
347
            ->save();
348
    }
349
350
    /**
351
     * @param QueryInterface|ElementInterface[] $objects
352
     * @return bool
353
     *
354
     * @deprecated use `add($object, $attributes)->save()`
355
     */
356
    public function associateMany($objects): bool
357
    {
358
        return $this->add($objects)
359
            ->save();
360
    }
361
362
363
    /*******************************************
364
     * DISSOCIATE
365
     *******************************************/
366
367
    /**
368
     * @noinspection PhpDocMissingThrowsInspection
369
     *
370
     * @param ActiveRecord|ElementInterface|int|array $object
371
     * @return bool
372
     *
373
     * @deprecated use `remove($object)->save()`
374
     */
375
    public function dissociateOne($object): bool
376
    {
377
        return $this->remove($object)
378
            ->save();
379
    }
380
381
    /**
382
     * @param QueryInterface|ElementInterface[] $objects
383
     * @return bool
384
     *
385
     * @deprecated use `remove($objects)->save()`
386
     */
387
    public function dissociateMany($objects): bool
388
    {
389
        return $this->remove($objects)
390
            ->save();
391
    }
392
393
394
    /*******************************************
395
     * CACHE
396
     *******************************************/
397
398
    /**
399
     * @param array $associations
400
     * @param bool $mutated
401
     * @return static
402
     */
403
    protected function newCollection(array $associations, bool $mutated = true): self
404
    {
405
        $this->collection = Collection::make($associations);
406
        $this->mutated = $mutated;
407
408
        return $this;
409
    }
410
411
    /**
412
     * @param $association
413
     * @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...
414
     */
415
    protected function addToCollection($association): self
416
    {
417
        if (null === $this->collection) {
418
            return $this->newCollection([$association], true);
419
        }
420
421
        $this->collection->push($association);
422
        $this->mutated = true;
423
424
        return $this;
425
    }
426
427
    /**
428
     * @param int $key
429
     * @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...
430
     */
431
    protected function removeFromCollection(int $key): self
432
    {
433
        $this->collection->forget($key);
434
        $this->mutated = true;
435
436
        return $this;
437
    }
438
439
440
    /*******************************************
441
     * UTILITIES
442
     *******************************************/
443
444
    /**
445
     * Ensure we're working with an array of objects, not configs, etc
446
     *
447
     * @param array|QueryInterface|Collection|ElementInterface|UserAssociation $objects
448
     * @return array
449
     */
450
    protected function objectArray($objects): array
451
    {
452
        if ($objects instanceof QueryInterface || $objects instanceof Collection) {
453
            $objects = $objects->all();
454
        }
455
456
        // proper array
457
        if (!is_array($objects) || ArrayHelper::isAssociative($objects)) {
458
            $objects = [$objects];
459
        }
460
461
        return array_filter($objects);
462
    }
463
}
464