Completed
Push — develop ( 7f03d4...6a69af )
by Nate
09:17
created

UserRelationship   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 306
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 44
lcom 1
cbo 12
dl 0
loc 306
ccs 0
cts 164
cp 0
rs 8.8798
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getCollection() 0 11 2
A createCollectionFromRelations() 0 25 4
A existingRelationships() 0 8 1
A elementQuery() 0 7 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 11 4
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\UserQuery;
12
use craft\elements\User;
13
use craft\helpers\ArrayHelper;
14
use flipbox\organizations\elements\Organization;
15
use flipbox\organizations\Organizations;
16
use flipbox\organizations\queries\UserAssociationQuery;
17
use flipbox\organizations\records\UserAssociation;
18
use Tightenco\Collect\Support\Collection;
19
20
/**
21
 * Manages Users associated to Organizations
22
 *
23
 * @author Flipbox Factory <[email protected]>
24
 * @since 2.0.0
25
 *
26
 * @method UserAssociation findOrCreate($object)
27
 * @method UserAssociation findOne($object = null)
28
 * @method UserAssociation findOrFail($object)
29
 */
30
class UserRelationship implements RelationshipInterface
31
{
32
    use RelationshipTrait;
33
34
    /**
35
     * @var Organization
36
     */
37
    private $organization;
38
39
    /**
40
     * @param Organization $organization
41
     */
42
    public function __construct(Organization $organization)
43
    {
44
        $this->organization = $organization;
45
    }
46
47
48
    /************************************************************
49
     * COLLECTION
50
     ************************************************************/
51
52
    /**
53
     * Get a collection of associated users
54
     *
55
     * @return User[]|Collection
56
     */
57
    public function getCollection(): Collection
58
    {
59
        if (null === $this->relations) {
60
            return new Collection(
61
                $this->elementQuery()
62
                    ->all()
63
            );
64
        }
65
66
        return $this->createCollectionFromRelations();
67
    }
68
69
    /**
70
     * @return Collection
71
     */
72
    protected function createCollectionFromRelations()
73
    {
74
        $ids = $this->getRelationships()->pluck('userId')->all();
75
        if (empty($ids)) {
76
            return $this->getRelationships()->pluck('user');
77
        }
78
79
        // 'eager' load where we'll pre-populate all of the elements
80
        $elements = $this->elementQuery()
81
            ->id($ids)
82
            ->indexBy('id')
83
            ->all();
84
85
        return $this->getRelationships()
86
            ->transform(function (UserAssociation $association) use ($elements) {
87
                if (!$association->isUserSet() && isset($elements[$association->getUserId()])) {
0 ignored issues
show
Documentation Bug introduced by
The method isUserSet does not exist on object<flipbox\organizat...ecords\UserAssociation>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
88
                    $association->setUser($elements[$association->getUserId()]);
89
                }
90
91
                $association->setOrganization($this->organization);
92
93
                return $association;
94
            })
95
            ->pluck('user');
96
    }
97
98
    /**
99
     * @return Collection
100
     */
101
    protected function existingRelationships(): Collection
102
    {
103
        $relationships = $this->associationQuery()
104
            ->with('types')
105
            ->all();
106
107
        return $this->createRelations($relationships);
108
    }
109
110
    /************************************************************
111
     * QUERY
112
     ************************************************************/
113
114
    /**
115
     * @return UserQuery
116
     */
117
    private function elementQuery(): UserQuery
118
    {
119
        return User::find()
120
            ->organizationId($this->organization->getId() ?: false)
121
            ->anyStatus()
122
            ->limit(null);
123
    }
124
125
    /**
126
     * @return UserAssociationQuery
127
     */
128
    private function associationQuery(): UserAssociationQuery
129
    {
130
        /** @noinspection PhpUndefinedMethodInspection */
131
        return UserAssociation::find()
132
            ->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...
133
            ->orderBy([
134
                'organizationOrder' => SORT_ASC
135
            ])
136
            ->limit(null);
137
    }
138
139
    /************************************************************
140
     * CREATE
141
     ************************************************************/
142
143
    /**
144
     * @param $object
145
     * @return UserAssociation
146
     */
147
    protected function create($object): UserAssociation
148
    {
149
        if ($object instanceof UserAssociation) {
150
            return $object;
151
        }
152
153
        return (new UserAssociation())
154
            ->setOrganization($this->organization)
155
            ->setUser($this->resolveObject($object));
156
    }
157
158
159
    /*******************************************
160
     * DELTA
161
     *******************************************/
162
163
    /**
164
     * @inheritDoc
165
     */
166
    protected function delta(): array
167
    {
168
        $existingAssociations = $this->associationQuery()
169
            ->indexBy('userId')
170
            ->all();
171
172
        $associations = [];
173
        $order = 1;
174
175
        /** @var UserAssociation $newAssociation */
176
        foreach ($this->getRelationships() as $newAssociation) {
177
            $association = ArrayHelper::remove(
178
                $existingAssociations,
179
                $newAssociation->getUserId()
180
            );
181
182
            $newAssociation->userOrder = $order++;
183
184
            /** @var UserAssociation $association */
185
            $association = $association ?: $newAssociation;
186
187
            // Has anything changed?
188
            if (!$association->getIsNewRecord() && !$this->hasChanged($newAssociation, $association)) {
189
                continue;
190
            }
191
192
            $associations[] = $this->sync($association, $newAssociation);
193
        }
194
195
        return [$associations, $existingAssociations];
196
    }
197
198
    /**
199
     * @param UserAssociation $new
200
     * @param UserAssociation $existing
201
     * @return bool
202
     */
203
    private function hasChanged(UserAssociation $new, UserAssociation $existing): bool
204
    {
205
        return (Organizations::getInstance()->getSettings()->getEnforceUserSortOrder() &&
206
                $new->userOrder != $existing->userOrder
207
            ) ||
208
            $new->state != $existing->state ||
209
            $new->getTypes()->isMutated();
210
    }
211
212
    /**
213
     * @param UserAssociation $from
214
     * @param UserAssociation $to
215
     *
216
     * @return UserAssociation
217
     */
218
    private function sync(UserAssociation $to, UserAssociation $from): UserAssociation
219
    {
220
        $to->userOrder = $from->userOrder;
221
        $to->state = $from->state;
222
223
        if ($from->getTypes()->isMutated()) {
224
            $to->getTypes()->clear()->add(
225
                $from->getTypes()->getCollection()
226
            );
227
        }
228
229
        $to->ignoreSortOrder();
230
231
        return $to;
232
    }
233
234
235
    /*******************************************
236
     * COLLECTION UTILS
237
     *******************************************/
238
239
    /**
240
     * @inheritDoc
241
     */
242
    protected function insertCollection(Collection $collection, UserAssociation $association)
243
    {
244
        if (Organizations::getInstance()->getSettings()->getEnforceUserSortOrder() && $association->userOrder > 0) {
245
            $collection->splice($association->userOrder - 1, 0, [$association]);
246
            return;
247
        }
248
249
        $collection->push($association);
250
    }
251
252
    /**
253
     * @inheritDoc
254
     */
255
    protected function updateCollection(Collection $collection, UserAssociation $association)
256
    {
257
        if (!Organizations::getInstance()->getSettings()->getEnforceUserSortOrder()) {
258
            return;
259
        }
260
261
        if (null !== ($key = $this->findKey($association))) {
262
            $collection->offsetUnset($key);
263
        }
264
265
        $this->insertCollection($collection, $association);
266
    }
267
268
269
    /*******************************************
270
     * UTILS
271
     *******************************************/
272
273
    /**
274
     * @param UserAssociation|User|int|array|null $object
275
     * @return int|null
276
     */
277
    protected function findKey($object = null)
278
    {
279
        if ($object instanceof UserAssociation) {
280
            if (!$object->getUser()) {
281
                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...
282
                return null;
283
            }
284
285
            return $this->findRelationshipKey($object->getUser()->email);
286
        }
287
288
        if (null === ($element = $this->resolveObject($object))) {
289
            return null;
290
        }
291
292
        return $this->findRelationshipKey($element->email);
293
    }
294
295
    /**
296
     * @param $identifier
297
     * @return int|string|null
298
     */
299
    private function findRelationshipKey($identifier)
300
    {
301
        /** @var UserAssociation $association */
302
        foreach ($this->getRelationships()->all() as $key => $association) {
303
            if (null !== $association->getUser() && $association->getUser()->email == $identifier) {
304
                return $key;
305
            }
306
        }
307
308
        return null;
309
    }
310
311
    /**
312
     * @param UserAssociation|User|int|array|null $user
313
     * @return User|null
314
     */
315
    protected function resolveObjectInternal($user)
316
    {
317
        if ($user instanceof UserAssociation) {
318
            return $user->getUser();
319
        }
320
321
        if ($user instanceof User) {
322
            return $user;
323
        }
324
325
        if (is_numeric($user)) {
326
            return \Craft::$app->getUsers()->getUserById($user);
327
        }
328
329
        if (is_string($user)) {
330
            return \Craft::$app->getUsers()->getUserByUsernameOrEmail($user);
331
        }
332
333
        return User::findOne($user);
0 ignored issues
show
Bug Compatibility introduced by
The expression \craft\elements\User::findOne($user); of type craft\base\Element|null|craft\base\Element[] adds the type craft\base\Element[] to the return on line 333 which is incompatible with the return type documented by flipbox\organizations\re...::resolveObjectInternal of type craft\elements\User|null.
Loading history...
334
    }
335
}
336