Completed
Push — develop ( 0f5a88...a06987 )
by Nate
04:58
created

RelationshipTrait   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 309
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 5
dl 0
loc 309
ccs 0
cts 132
cp 0
rs 9.44
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
findKey() 0 1 ?
create() 0 1 ?
resolveObject() 0 1 ?
existingRelationships() 0 1 ?
delta() 0 1 ?
A findOrCreate() 0 8 2
A findOrFail() 0 8 2
A findOne() 0 8 2
A exists() 0 4 1
A getRelationships() 0 8 2
A add() 0 20 4
A remove() 0 10 3
A reset() 0 6 1
A clear() 0 5 1
B save() 0 27 6
A newRelations() 0 7 1
A addToRelations() 0 11 2
A removeFromRelations() 0 7 1
A resolve() 0 14 4
A objectArray() 0 13 5
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 resolveObject($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
     *
67
     * @return Collection[]
68
     */
69
    abstract protected function delta(): array;
70
71
    /**
72
     * @param ActiveRecord|ElementInterface|int|string $object
73
     * @return ActiveRecord
74
     */
75
    public function findOrCreate($object): ActiveRecord
76
    {
77
        if (null === ($association = $this->findOne($object))) {
78
            $association = $this->create($object);
79
        }
80
81
        return $association;
82
    }
83
84
    /**
85
     * @param ActiveRecord|ElementInterface|int|string $object
86
     * @return ActiveRecord
87
     * @throws Exception
88
     */
89
    public function findOrFail($object): ActiveRecord
90
    {
91
        if (null === ($association = $this->findOne($object))) {
92
            throw new Exception("Association could not be found.");
93
        }
94
95
        return $association;
96
    }
97
98
    /**
99
     * @param ActiveRecord|ElementInterface|int|string|null $object
100
     * @return ActiveRecord|null
101
     */
102
    public function findOne($object = null)
103
    {
104
        if (null === ($key = $this->findKey($object))) {
105
            return null;
106
        }
107
108
        return $this->relations->get($key);
109
    }
110
111
    /**
112
     * @param ActiveRecord|ElementInterface|int|string $object
113
     * @return bool
114
     */
115
    public function exists($object): bool
116
    {
117
        return null !== $this->findKey($object);
118
    }
119
120
121
    /************************************************************
122
     * COLLECTIONS
123
     ************************************************************/
124
125
    /**
126
     * @inheritDoc
127
     */
128
    public function getRelationships(): Collection
129
    {
130
        if (null === $this->relations) {
131
            $this->relations = $this->existingRelationships();
132
        }
133
134
        return $this->relations;
135
    }
136
137
    /************************************************************
138
     * ADD / REMOVE
139
     ************************************************************/
140
141
    /**
142
     * Add one or many object relations (but do not save)
143
     *
144
     * @param $objects
145
     * @param array $attributes
146
     * @return RelationshipInterface
147
     */
148
    public function add($objects, array $attributes = []): RelationshipInterface
149
    {
150
        foreach ($this->objectArray($objects) as $object) {
151
            if (null === ($association = $this->findOne($object))) {
152
                $association = $this->create($object);
153
                $this->addToRelations($association);
154
            }
155
156
            if (!empty($attributes)) {
157
                Craft::configure(
158
                    $association,
159
                    $attributes
160
                );
161
162
                $this->mutated = true;
163
            }
164
        }
165
166
        return $this;
167
    }
168
169
    /**
170
     * @param $objects
171
     * @return RelationshipInterface
172
     */
173
    public function remove($objects): RelationshipInterface
174
    {
175
        foreach ($this->objectArray($objects) as $object) {
176
            if (null !== ($key = $this->findKey($object))) {
177
                $this->removeFromRelations($key);
178
            }
179
        }
180
181
        return $this;
182
    }
183
184
185
    /************************************************************
186
     * RESET
187
     ************************************************************/
188
189
    /**
190
     * Reset associations
191
     * @return RelationshipInterface
192
     */
193
    public function reset(): RelationshipInterface
194
    {
195
        $this->relations = null;
196
        $this->mutated = false;
197
        return $this;
198
    }
199
200
    /**
201
     * Reset associations
202
     * @return RelationshipInterface
203
     */
204
    public function clear(): RelationshipInterface
205
    {
206
        $this->newRelations([]);
207
        return $this;
208
    }
209
210
211
    /*******************************************
212
     * SAVE
213
     *******************************************/
214
215
    /**
216
     * @return bool
217
     */
218
    public function save(): bool
219
    {
220
        // No changes?
221
        if (!$this->isMutated()) {
222
            return true;
223
        }
224
225
        $success = true;
226
227
        list($save, $delete) = $this->delta();
228
229
        foreach ($delete as $relationship) {
230
            if (!$relationship->delete()) {
231
                $success = false;
232
            }
233
        }
234
235
        foreach ($save as $relationship) {
236
            if (!$relationship->save()) {
237
                $success = false;
238
            }
239
        }
240
241
        $this->newRelations($save, false);
242
243
        return $success;
244
    }
245
246
247
    /*******************************************
248
     * CACHE
249
     *******************************************/
250
251
    /**
252
     * @param array $associations
253
     * @param bool $mutated
254
     * @return static
255
     */
256
    protected function newRelations(array $associations, bool $mutated = true): self
257
    {
258
        $this->relations = Collection::make($associations);
259
        $this->mutated = $mutated;
260
261
        return $this;
262
    }
263
264
    /**
265
     * @param $association
266
     * @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...
267
     */
268
    protected function addToRelations($association): self
269
    {
270
        if (null === $this->relations) {
271
            return $this->newRelations([$association], true);
272
        }
273
274
        $this->relations->push($association);
275
        $this->mutated = true;
276
277
        return $this;
278
    }
279
280
    /**
281
     * @param int $key
282
     * @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...
283
     */
284
    protected function removeFromRelations(int $key): self
285
    {
286
        $this->relations->forget($key);
287
        $this->mutated = true;
288
289
        return $this;
290
    }
291
292
293
    /*******************************************
294
     * UTILITIES
295
     *******************************************/
296
297
    /**
298
     * @inheritDoc
299
     */
300
    protected function resolve($object = null)
301
    {
302
        if (null === $object) {
303
            return null;
304
        }
305
306
        if (is_array($object) &&
307
            null !== ($id = ArrayHelper::getValue($object, 'id'))
308
        ) {
309
            $object = ['id' => $id];
310
        }
311
312
        return $this->resolveObject($object);
313
    }
314
315
    /**
316
     * Ensure we're working with an array of objects, not configs, etc
317
     *
318
     * @param array|QueryInterface|Collection|ElementInterface|UserAssociation $objects
319
     * @return array
320
     */
321
    protected function objectArray($objects): array
322
    {
323
        if ($objects instanceof QueryInterface || $objects instanceof Collection) {
324
            $objects = $objects->all();
325
        }
326
327
        // proper array
328
        if (!is_array($objects) || ArrayHelper::isAssociative($objects)) {
329
            $objects = [$objects];
330
        }
331
332
        return array_filter($objects);
333
    }
334
}
335