Completed
Push — develop ( e5b284...0f5a88 )
by Nate
05:39
created

RelationshipTrait::newRelations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 0
cts 6
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
     * @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
     * @param ActiveRecord|ElementInterface|int|string|null $object
92
     * @return ActiveRecord|null
93
     */
94
    public function findOne($object = null)
95
    {
96
        if (null === ($key = $this->findKey($object))) {
97
            return null;
98
        }
99
100
        return $this->relations->get($key);
101
    }
102
103
    /**
104
     * @param ActiveRecord|ElementInterface|int|string $object
105
     * @return bool
106
     */
107
    public function exists($object): bool
108
    {
109
        return null !== $this->findKey($object);
110
    }
111
112
113
    /************************************************************
114
     * COLLECTIONS
115
     ************************************************************/
116
117
    /**
118
     * @inheritDoc
119
     */
120
    public function getRelationships(): Collection
121
    {
122
        if (null === $this->relations) {
123
            $this->relations = new Collection(
124
                $this->query()->all()
125
            );
126
        }
127
128
        return $this->relations;
129
    }
130
131
132
    /************************************************************
133
     * ADD / REMOVE
134
     ************************************************************/
135
136
    /**
137
     * Add one or many object relations (but do not save)
138
     *
139
     * @param $objects
140
     * @param array $attributes
141
     * @return RelationshipInterface
142
     */
143
    public function add($objects, array $attributes = []): RelationshipInterface
144
    {
145
        foreach ($this->objectArray($objects) as $object) {
146
            if (null === ($association = $this->findOne($object))) {
147
                $association = $this->create($object);
148
                $this->addToRelations($association);
149
            }
150
151
            if (!empty($attributes)) {
152
                Craft::configure(
153
                    $association,
154
                    $attributes
155
                );
156
157
                $this->mutated = true;
158
            }
159
        }
160
161
        return $this;
162
    }
163
164
    /**
165
     * @param $objects
166
     * @return RelationshipInterface
167
     */
168
    public function remove($objects): RelationshipInterface
169
    {
170
        foreach ($this->objectArray($objects) as $object) {
171
            if (null !== ($key = $this->findKey($object))) {
172
                $this->removeFromRelations($key);
173
            }
174
        }
175
176
        return $this;
177
    }
178
179
180
    /************************************************************
181
     * RESET
182
     ************************************************************/
183
184
    /**
185
     * Reset associations
186
     * @return RelationshipInterface
187
     */
188
    public function reset(): RelationshipInterface
189
    {
190
        $this->relations = null;
191
        $this->mutated = false;
192
        return $this;
193
    }
194
195
    /**
196
     * Reset associations
197
     * @return RelationshipInterface
198
     */
199
    public function clear(): RelationshipInterface
200
    {
201
        $this->newRelations([]);
202
        return $this;
203
    }
204
205
206
    /*******************************************
207
     * SAVE
208
     *******************************************/
209
210
    /**
211
     * @return bool
212
     */
213
    public function save(): bool
214
    {
215
        // No changes?
216
        if (!$this->isMutated()) {
217
            return true;
218
        }
219
220
        $success = true;
221
222
        list($newAssociations, $existingAssociations) = $this->associationDelta();
223
224
        // Delete those removed
225
        foreach ($existingAssociations as $existingAssociation) {
226
            if (!$existingAssociation->delete()) {
227
                $success = false;
228
            }
229
        }
230
231
        foreach ($newAssociations as $newAssociation) {
232
            if (!$newAssociation->save()) {
233
                $success = false;
234
            }
235
        }
236
237
        $this->newRelations($newAssociations);
238
        $this->mutated = false;
239
240
        if (!$success) {
241
            $this->handleAssociationError();
242
        }
243
244
        return $success;
245
    }
246
247
248
    /*******************************************
249
     * CACHE
250
     *******************************************/
251
252
    /**
253
     * @param array $associations
254
     * @param bool $mutated
255
     * @return static
256
     */
257
    protected function newRelations(array $associations, bool $mutated = true): self
258
    {
259
        $this->relations = Collection::make($associations);
260
        $this->mutated = $mutated;
261
262
        return $this;
263
    }
264
265
    /**
266
     * @param $association
267
     * @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...
268
     */
269
    protected function addToRelations($association): self
270
    {
271
        if (null === $this->relations) {
272
            return $this->newRelations([$association], true);
273
        }
274
275
        $this->relations->push($association);
276
        $this->mutated = true;
277
278
        return $this;
279
    }
280
281
    /**
282
     * @param int $key
283
     * @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...
284
     */
285
    protected function removeFromRelations(int $key): self
286
    {
287
        $this->relations->forget($key);
288
        $this->mutated = true;
289
290
        return $this;
291
    }
292
293
294
    /*******************************************
295
     * UTILITIES
296
     *******************************************/
297
298
    /**
299
     * Ensure we're working with an array of objects, not configs, etc
300
     *
301
     * @param array|QueryInterface|Collection|ElementInterface|UserAssociation $objects
302
     * @return array
303
     */
304
    protected function objectArray($objects): array
305
    {
306
        if ($objects instanceof QueryInterface || $objects instanceof Collection) {
307
            $objects = $objects->all();
308
        }
309
310
        // proper array
311
        if (!is_array($objects) || ArrayHelper::isAssociative($objects)) {
312
            $objects = [$objects];
313
        }
314
315
        return array_filter($objects);
316
    }
317
}
318