UserTypesBehavior   B
last analyzed

Complexity

Total Complexity 46

Size/Duplication

Total Lines 316
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 12
dl 0
loc 316
ccs 0
cts 186
cp 0
rs 8.72
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A userTypeQuery() 0 14 2
A getUserTypes() 0 15 3
A setUserTypes() 0 13 2
A addUserTypes() 0 21 4
A addUserType() 0 10 1
A resolveUserType() 0 12 3
C saveUserTypes() 0 55 11
B associateUserTypes() 0 40 8
B dissociateUserTypes() 0 38 8
A associationIdQuery() 0 10 1
A associationId() 0 5 3

How to fix   Complexity   

Complex Class

Complex classes like UserTypesBehavior 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 UserTypesBehavior, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace flipbox\organizations\behaviors;
4
5
use craft\elements\User;
6
use craft\helpers\ArrayHelper;
7
use craft\helpers\Json;
8
use flipbox\craft\ember\helpers\QueryHelper;
9
use flipbox\organizations\elements\Organization;
10
use flipbox\organizations\Organizations as OrganizationPlugin;
11
use flipbox\organizations\queries\UserTypeQuery;
12
use flipbox\organizations\records\UserAssociation;
13
use flipbox\organizations\records\UserType;
14
use flipbox\organizations\records\UserTypeAssociation;
15
use yii\base\Behavior;
16
use yii\db\Query;
17
18
/**
19
 * @property User $owner;
20
 */
21
class UserTypesBehavior extends Behavior
22
{
23
    /**
24
     * @var UserTypeQuery|null
25
     */
26
    private $userTypes;
27
28
    /**
29
     * @param array $criteria
30
     * @return UserTypeQuery
31
     */
32
    public function userTypeQuery($criteria = []): UserTypeQuery
33
    {
34
        $query = UserType::find()
35
            ->user($this->owner);
0 ignored issues
show
Bug introduced by nateiler
It seems like $this->owner can also be of type object<yii\base\Component>; however, flipbox\craft\ember\quer...rAttributeTrait::user() does only seem to accept string|array<integer,str...ft\elements\User>>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
36
37
        if (!empty($criteria)) {
38
            QueryHelper::configure(
39
                $query,
40
                $criteria
41
            );
42
        }
43
44
        return $query;
45
    }
46
47
    /**
48
     * Get a query with associated user types
49
     *
50
     * @param array $criteria
51
     * @return UserTypeQuery
52
     */
53
    public function getUserTypes($criteria = []): UserTypeQuery
54
    {
55
        if (null === $this->userTypes) {
56
            $this->userTypes = $this->userTypeQuery();
57
        }
58
59
        if (!empty($criteria)) {
60
            QueryHelper::configure(
61
                $this->userTypes,
62
                $criteria
63
            );
64
        }
65
66
        return $this->userTypes;
67
    }
68
69
    /**
70
     * Set an array or query of user types to a user
71
     *
72
     * @param $userTypes
73
     * @return $this
74
     */
75
    public function setUserTypes($userTypes)
76
    {
77
        if ($userTypes instanceof UserTypeQuery) {
78
            $this->userTypes = $userTypes;
79
            return $this;
80
        }
81
82
        // Reset the query
83
        $this->userTypes = $this->userTypeQuery();
84
        $this->userTypes->setCachedResult([]);
85
        $this->addUserTypes($userTypes);
86
        return $this;
87
    }
88
89
    /**
90
     * Add an array of user types to a user.  Note: This does not save the user type associations.
91
     *
92
     * @param $types
93
     * @return $this
94
     */
95
    protected function addUserTypes(array $types)
96
    {
97
        // In case a config is directly passed
98
        if (ArrayHelper::isAssociative($types)) {
99
            $types = [$types];
100
        }
101
102
        foreach ($types as $key => $type) {
103
            if (!$type = $this->resolveUserType($type)) {
104
                OrganizationPlugin::warning(sprintf(
105
                    "Unable to resolve user type: %s",
106
                    (string)Json::encode($type)
107
                ));
108
                continue;
109
            }
110
111
            $this->addUserType($type);
112
        }
113
114
        return $this;
115
    }
116
117
    /**
118
     * Add a user type to a user.  Note: This does not save the user type associations.
119
     *
120
     * @param UserType $type
121
     * @return $this
122
     */
123
    public function addUserType(UserType $type)
124
    {
125
        // Current associated types
126
        $allTypes = $this->getUserTypes()->all();
127
        $allTypes[] = $type;
128
129
        $this->getUserTypes()->setCachedResult($allTypes);
130
131
        return $this;
132
    }
133
134
    /**
135
     * @param mixed $type
136
     * @return UserType
137
     */
138
    protected function resolveUserType($type): UserType
139
    {
140
        if (null !== ($type = UserType::findOne($type))) {
141
            return $type;
142
        }
143
144
        if (!is_array($type)) {
145
            $type = ArrayHelper::toArray($type, [], false);
146
        }
147
148
        return new UserType($type);
149
    }
150
151
    /*******************************************
152
     * ASSOCIATE and/or DISASSOCIATE
153
     *******************************************/
154
155
    /**
156
     * @param UserTypeQuery $query
157
     * @param Organization $organization
158
     * @return bool
159
     * @throws \Throwable
160
     * @throws \yii\db\StaleObjectException
161
     */
162
    public function saveUserTypes(UserTypeQuery $query, Organization $organization): bool
163
    {
164
        // No changes?
165
        if (null === ($types = $query->getCachedResult())) {
166
            return true;
167
        }
168
169
        // Determine an association
170
        if (null === ($userAssociationId = $this->associationId($this->owner->getId(), $organization->getId()))) {
171
            $this->owner->addError('types', 'User is not associated to organization.');
172
            return false;
173
        }
174
175
        $currentAssociations = UserTypeAssociation::find()
176
            ->userId($userAssociationId ?: false)
177
            ->indexBy('typeId')
178
            ->all();
179
180
        $success = true;
181
        $associations = [];
182
        $order = 1;
183
        foreach ($types as $type) {
184
            if (null === ($association = ArrayHelper::remove($currentAssociations, $type->getId()))) {
185
                $association = (new UserTypeAssociation())
186
                    ->setType($type);
187
                $association->userId = $userAssociationId;
188
            }
189
190
            $association->sortOrder = $order++;
191
192
            $associations[] = $association;
193
        }
194
195
        // Delete anything that has been removed
196
        foreach ($currentAssociations as $currentAssociation) {
197
            if (!$currentAssociation->delete()) {
198
                $success = false;
199
            }
200
        }
201
202
        // Save'em
203
        foreach ($associations as $association) {
204
            if (!$association->save()) {
205
                $success = false;
206
            }
207
        }
208
209
        if (!$success) {
210
            $this->owner->addError('types', 'Unable to save user types.');
211
        }
212
213
        $this->setUserTypes($query);
214
215
        return $success;
216
    }
217
218
    /**
219
     * @param UserTypeQuery $query
220
     * @param Organization $organization
221
     * @return bool
222
     * @throws \Throwable
223
     */
224
    public function associateUserTypes(UserTypeQuery $query, Organization $organization): bool
225
    {
226
        $records = $query->all();
227
228
        if (empty($records)) {
229
            return true;
230
        }
231
232
        // Determine an association
233
        if (null === ($userAssociationId = $this->associationId($this->owner->getId(), $organization->getId()))) {
234
            $this->owner->addError('types', 'User is not associated to organization.');
235
            return false;
236
        }
237
238
        $currentAssociations = UserTypeAssociation::find()
239
            ->userId($userAssociationId ?: false)
240
            ->indexBy('typeId')
241
            ->all();
242
243
        $success = true;
244
        foreach ($records as $record) {
245
            if (null === ($association = ArrayHelper::remove($currentAssociations, $record->getId()))) {
246
                $association = (new UserTypeAssociation())
247
                    ->setType($record);
248
                $association->userId = $userAssociationId;
249
            }
250
251
            if (!$association->save()) {
252
                $success = false;
253
            }
254
        }
255
256
        if (!$success) {
257
            $this->owner->addError('users', 'Unable to associate user types.');
258
        }
259
260
        $this->userTypes = null;
261
262
        return $success;
263
    }
264
265
    /**
266
     * @param UserTypeQuery $query
267
     * @param Organization $organization
268
     * @return bool
269
     * @throws \Throwable
270
     */
271
    public function dissociateUserTypes(UserTypeQuery $query, Organization $organization): bool
272
    {
273
        $records = $query->all();
274
275
        if (empty($records)) {
276
            return true;
277
        }
278
279
        // Determine an association
280
        if (null === ($userAssociationId = $this->associationId($this->owner->getId(), $organization->getId()))) {
281
            $this->owner->addError('types', 'User is not associated to organization.');
282
            return false;
283
        }
284
285
        $currentAssociations = UserTypeAssociation::find()
286
            ->userId($userAssociationId ?: false)
287
            ->indexBy('typeId')
288
            ->all();
289
290
        $success = true;
291
        foreach ($records as $record) {
292
            if (null === ($association = ArrayHelper::remove($currentAssociations, $record->getId()))) {
293
                continue;
294
            }
295
296
            if (!$association->delete()) {
297
                $success = false;
298
            }
299
        }
300
301
        if (!$success) {
302
            $this->owner->addError('users', 'Unable to dissociate user types.');
303
        }
304
305
        $this->userTypes = null;
306
307
        return $success;
308
    }
309
310
    /**
311
     * @param int $userId
312
     * @param int $organizationId
313
     * @return Query
314
     */
315
    protected function associationIdQuery(int $userId, int $organizationId): Query
316
    {
317
        return (new Query())
318
            ->select(['id'])
319
            ->from([UserAssociation::tableName()])
320
            ->where([
321
                'organizationId' => $organizationId,
322
                'userId' => $userId,
323
            ]);
324
    }
325
326
    /**
327
     * @param int $userId
328
     * @param int $organizationId
329
     * @return string|null
330
     */
331
    protected function associationId(int $userId, int $organizationId)
332
    {
333
        $id = $this->associationIdQuery($userId, $organizationId)->scalar();
334
        return is_string($id) || is_numeric($id) ? $id : null;
335
    }
336
}
337