UserRelationship   B
last analyzed

Complexity

Total Complexity 45

Size/Duplication

Total Lines 313
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 45
lcom 1
cbo 12
dl 0
loc 313
ccs 0
cts 169
cp 0
rs 8.8
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getCollection() 0 13 2
A createCollectionFromRelations() 0 27 4
A existingRelationships() 0 8 1
A getQuery() 0 5 2
A associationQuery() 0 10 2
A create() 0 10 2
A delta() 0 31 5
A hasChanged() 0 8 4
A sync() 0 15 2
A insertCollection() 0 9 3
A updateCollection() 0 12 3
A findKey() 0 17 4
A findRelationshipKey() 0 15 5
A resolveObjectInternal() 0 20 5

How to fix   Complexity   

Complex Class

Complex classes like UserRelationship often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UserRelationship, and based on these observations, apply Extract Interface, too.

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\elements\db\ElementQueryInterface;
12
use craft\elements\db\UserQuery;
13
use craft\elements\User;
14
use craft\helpers\ArrayHelper;
15
use flipbox\organizations\elements\Organization;
16
use flipbox\organizations\Organizations;
17
use flipbox\organizations\queries\OrganizationQuery;
18
use flipbox\organizations\queries\UserAssociationQuery;
19
use flipbox\organizations\records\UserAssociation;
20
use Tightenco\Collect\Support\Collection;
21
22
/**
23
 * Manages Users associated to Organizations
24
 *
25
 * @author Flipbox Factory <[email protected]>
26
 * @since 2.0.0
27
 *
28
 * @method UserAssociation findOrCreate($object)
29
 * @method UserAssociation findOne($object = null)
30
 * @method UserAssociation findOrFail($object)
31
 */
32
class UserRelationship implements ElementRelationshipInterface
33
{
34
    use RelationshipTrait;
35
36
    /**
37
     * @var Organization
38
     */
39
    private $organization;
40
41
    /**
42
     * @param Organization $organization
43
     */
44
    public function __construct(Organization $organization)
45
    {
46
        $this->organization = $organization;
47
    }
48
49
50
    /************************************************************
51
     * COLLECTION
52
     ************************************************************/
53
54
    /**
55
     * Get a collection of associated users
56
     *
57
     * @return User[]|Collection
58
     */
59
    public function getCollection(): Collection
60
    {
61
        if (null === $this->relations) {
62
            return new Collection(
63
                $this->getQuery()
64
                    ->anyStatus()
65
                    ->limit(null)
66
                    ->all()
67
            );
68
        }
69
70
        return $this->createCollectionFromRelations();
71
    }
72
73
    /**
74
     * @return Collection
75
     */
76
    protected function createCollectionFromRelations()
77
    {
78
        $ids = $this->getRelationships()->pluck('userId')->all();
79
        if (empty($ids)) {
80
            return $this->getRelationships()->pluck('user');
81
        }
82
83
        // 'eager' load where we'll pre-populate all of the elements
84
        $elements = $this->getQuery()
85
            ->id($ids)
86
            ->indexBy('id')
87
            ->anyStatus()
88
            ->limit(null)
89
            ->all();
90
91
        return $this->getRelationships()
92
            ->transform(function (UserAssociation $association) use ($elements) {
93
                if (!$association->isUserSet() && isset($elements[$association->getUserId()])) {
94
                    $association->setUser($elements[$association->getUserId()]);
95
                }
96
97
                $association->setOrganization($this->organization);
98
99
                return $association;
100
            })
101
            ->pluck('user');
102
    }
103
104
    /**
105
     * @return Collection
106
     */
107
    protected function existingRelationships(): Collection
108
    {
109
        $relationships = $this->associationQuery()
110
            ->with('types')
111
            ->all();
112
113
        return $this->createRelations($relationships);
114
    }
115
116
    /************************************************************
117
     * QUERY
118
     ************************************************************/
119
120
    /**
121
     * @inheritDoc
122
     * @return UserQuery
123
     */
124
    public function getQuery(): ElementQueryInterface
125
    {
126
        return User::find()
127
            ->organizationId($this->organization->getId() ?: false);
128
    }
129
130
    /**
131
     * @return UserAssociationQuery
132
     */
133
    private function associationQuery(): UserAssociationQuery
134
    {
135
        /** @noinspection PhpUndefinedMethodInspection */
136
        return UserAssociation::find()
137
            ->setOrganizationId($this->organization->getId() ?: false)
0 ignored issues
show
Security Bug introduced by
It seems like $this->organization->getId() ?: false can also be of type false; however, flipbox\organizations\qu...it::setOrganizationId() does only seem to accept string|array<integer,str...nts\Organization>>|null, did you maybe forget to handle an error condition?
Loading history...
138
            ->orderBy([
139
                'organizationOrder' => SORT_ASC
140
            ])
141
            ->limit(null);
142
    }
143
144
    /************************************************************
145
     * CREATE
146
     ************************************************************/
147
148
    /**
149
     * @param $object
150
     * @return UserAssociation
151
     */
152
    protected function create($object): UserAssociation
153
    {
154
        if ($object instanceof UserAssociation) {
155
            return $object;
156
        }
157
158
        return (new UserAssociation())
159
            ->setOrganization($this->organization)
160
            ->setUser($this->resolveObject($object));
161
    }
162
163
164
    /*******************************************
165
     * DELTA
166
     *******************************************/
167
168
    /**
169
     * @inheritDoc
170
     */
171
    protected function delta(): array
172
    {
173
        $existingAssociations = $this->associationQuery()
174
            ->indexBy('userId')
175
            ->all();
176
177
        $associations = [];
178
        $order = 1;
179
180
        /** @var UserAssociation $newAssociation */
181
        foreach ($this->getRelationships() as $newAssociation) {
182
            $association = ArrayHelper::remove(
183
                $existingAssociations,
184
                $newAssociation->getUserId()
185
            );
186
187
            $newAssociation->userOrder = $order++;
188
189
            /** @var UserAssociation $association */
190
            $association = $association ?: $newAssociation;
191
192
            // Has anything changed?
193
            if (!$association->getIsNewRecord() && !$this->hasChanged($newAssociation, $association)) {
194
                continue;
195
            }
196
197
            $associations[] = $this->sync($association, $newAssociation);
198
        }
199
200
        return [$associations, $existingAssociations];
201
    }
202
203
    /**
204
     * @param UserAssociation $new
205
     * @param UserAssociation $existing
206
     * @return bool
207
     */
208
    private function hasChanged(UserAssociation $new, UserAssociation $existing): bool
209
    {
210
        return (Organizations::getInstance()->getSettings()->getEnforceUserSortOrder() &&
211
                $new->userOrder != $existing->userOrder
212
            ) ||
213
            $new->state != $existing->state ||
214
            $new->getTypes()->isMutated();
215
    }
216
217
    /**
218
     * @param UserAssociation $from
219
     * @param UserAssociation $to
220
     *
221
     * @return UserAssociation
222
     */
223
    private function sync(UserAssociation $to, UserAssociation $from): UserAssociation
224
    {
225
        $to->userOrder = $from->userOrder;
226
        $to->state = $from->state;
227
228
        if ($from->getTypes()->isMutated()) {
229
            $to->getTypes()->clear()->add(
230
                $from->getTypes()->getCollection()
231
            );
232
        }
233
234
        $to->ignoreSortOrder();
235
236
        return $to;
237
    }
238
239
240
    /*******************************************
241
     * COLLECTION UTILS
242
     *******************************************/
243
244
    /**
245
     * @inheritDoc
246
     */
247
    protected function insertCollection(Collection $collection, UserAssociation $association)
248
    {
249
        if (Organizations::getInstance()->getSettings()->getEnforceUserSortOrder() && $association->userOrder > 0) {
250
            $collection->splice($association->userOrder - 1, 0, [$association]);
251
            return;
252
        }
253
254
        $collection->push($association);
255
    }
256
257
    /**
258
     * @inheritDoc
259
     */
260
    protected function updateCollection(Collection $collection, UserAssociation $association)
261
    {
262
        if (!Organizations::getInstance()->getSettings()->getEnforceUserSortOrder()) {
263
            return;
264
        }
265
266
        if (null !== ($key = $this->findKey($association))) {
267
            $collection->offsetUnset($key);
268
        }
269
270
        $this->insertCollection($collection, $association);
271
    }
272
273
274
    /*******************************************
275
     * UTILS
276
     *******************************************/
277
278
    /**
279
     * @param UserAssociation|User|int|array|null $object
280
     * @return int|null
281
     */
282
    protected function findKey($object = null)
283
    {
284
        if ($object instanceof UserAssociation) {
285
            if (!$object->getUser()) {
286
                var_dump("NOT FOUND");
0 ignored issues
show
Security Debugging Code introduced by
var_dump('NOT FOUND'); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
287
                return null;
288
            }
289
290
            return $this->findRelationshipKey($object->getUser()->email);
291
        }
292
293
        if (null === ($element = $this->resolveObject($object))) {
294
            return null;
295
        }
296
297
        return $this->findRelationshipKey($element->email);
298
    }
299
300
    /**
301
     * @param $identifier
302
     * @return int|string|null
303
     */
304
    private function findRelationshipKey($identifier)
305
    {
306
        if (null === $identifier) {
307
            return null;
308
        }
309
310
        /** @var UserAssociation $association */
311
        foreach ($this->getRelationships()->all() as $key => $association) {
312
            if (null !== $association->getUser() && $association->getUser()->email == $identifier) {
313
                return $key;
314
            }
315
        }
316
317
        return null;
318
    }
319
320
    /**
321
     * @param UserAssociation|User|int|array|null $user
322
     * @return User|null
323
     */
324
    protected function resolveObjectInternal($user)
325
    {
326
        if ($user instanceof UserAssociation) {
327
            return $user->getUser();
328
        }
329
330
        if ($user instanceof User) {
331
            return $user;
332
        }
333
334
        if (is_numeric($user)) {
335
            return \Craft::$app->getUsers()->getUserById($user);
336
        }
337
338
        if (is_string($user)) {
339
            return \Craft::$app->getUsers()->getUserByUsernameOrEmail($user);
340
        }
341
342
        return User::findOne($user);
0 ignored issues
show
Bug Compatibility introduced by
The expression \craft\elements\User::findOne($user); of type craft\base\ElementInterface|array|null adds the type array to the return on line 342 which is incompatible with the return type documented by flipbox\organizations\re...::resolveObjectInternal of type craft\elements\User|null.
Loading history...
343
    }
344
}
345