Completed
Push — develop ( fcce06...87d9a9 )
by Nate
02:07
created

UserTypesBehavior   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 12
dl 0
loc 327
ccs 0
cts 194
cp 0
rs 8.48
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 66 14
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
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
        // Determine an association
165
        if (null === ($userAssociationId = $this->associationId($this->owner->getId(), $organization->getId()))) {
166
            $this->owner->addError('types', 'User is not associated to organization.');
167
            return false;
168
        }
169
170
        $currentAssociations = UserTypeAssociation::find()
171
            ->userId($userAssociationId ?: false)
172
            ->indexBy('typeId')
173
            ->all();
174
175
        $success = true;
176
177
        if (null === ($types = $query->getCachedResult())) {
178
            // Delete anything that's currently set
179
            foreach ($currentAssociations as $currentAssociation) {
180
                if (!$currentAssociation->delete()) {
181
                    $success = false;
182
                }
183
            }
184
185
            if (!$success) {
186
                $this->owner->addError('types', 'Unable to dissociate types.');
187
            }
188
189
            return $success;
190
        }
191
192
        $associations = [];
193
        $order = 1;
194
        foreach ($types as $type) {
195
            if (null === ($association = ArrayHelper::remove($currentAssociations, $type->getId()))) {
196
                $association = (new UserTypeAssociation())
197
                    ->setType($type);
198
                $association->userId = $userAssociationId;
199
            }
200
201
            $association->sortOrder = $order++;
202
203
            $associations[] = $association;
204
        }
205
206
        // Delete anything that has been removed
207
        foreach ($currentAssociations as $currentAssociation) {
208
            if (!$currentAssociation->delete()) {
209
                $success = false;
210
            }
211
        }
212
213
        // Save'em
214
        foreach ($associations as $association) {
215
            if (!$association->save()) {
216
                $success = false;
217
            }
218
        }
219
220
        if (!$success) {
221
            $this->owner->addError('types', 'Unable to save user types.');
222
        }
223
224
        $this->setUserTypes($query);
225
226
        return $success;
227
    }
228
229
    /**
230
     * @param UserTypeQuery $query
231
     * @param Organization $organization
232
     * @return bool
233
     * @throws \Throwable
234
     */
235
    public function associateUserTypes(UserTypeQuery $query, Organization $organization): bool
236
    {
237
        $records = $query->all();
238
239
        if (empty($records)) {
240
            return true;
241
        }
242
243
        // Determine an association
244
        if (null === ($userAssociationId = $this->associationId($this->owner->getId(), $organization->getId()))) {
245
            $this->owner->addError('types', 'User is not associated to organization.');
246
            return false;
247
        }
248
249
        $currentAssociations = UserTypeAssociation::find()
250
            ->userId($userAssociationId ?: false)
251
            ->indexBy('typeId')
252
            ->all();
253
254
        $success = true;
255
        foreach ($records as $record) {
256
            if (null === ($association = ArrayHelper::remove($currentAssociations, $record->getId()))) {
257
                $association = (new UserTypeAssociation())
258
                    ->setType($record);
259
                $association->userId = $userAssociationId;
260
            }
261
262
            if (!$association->save()) {
263
                $success = false;
264
            }
265
        }
266
267
        if (!$success) {
268
            $this->owner->addError('users', 'Unable to associate user types.');
269
        }
270
271
        $this->userTypes = null;
272
273
        return $success;
274
    }
275
276
    /**
277
     * @param UserTypeQuery $query
278
     * @param Organization $organization
279
     * @return bool
280
     * @throws \Throwable
281
     */
282
    public function dissociateUserTypes(UserTypeQuery $query, Organization $organization): bool
283
    {
284
        $records = $query->all();
285
286
        if (empty($records)) {
287
            return true;
288
        }
289
290
        // Determine an association
291
        if (null === ($userAssociationId = $this->associationId($this->owner->getId(), $organization->getId()))) {
292
            $this->owner->addError('types', 'User is not associated to organization.');
293
            return false;
294
        }
295
296
        $currentAssociations = UserTypeAssociation::find()
297
            ->userId($userAssociationId ?: false)
298
            ->indexBy('typeId')
299
            ->all();
300
301
        $success = true;
302
        foreach ($records as $record) {
303
            if (null === ($association = ArrayHelper::remove($currentAssociations, $record->getId()))) {
304
                continue;
305
            }
306
307
            if (!$association->delete()) {
308
                $success = false;
309
            }
310
        }
311
312
        if (!$success) {
313
            $this->owner->addError('users', 'Unable to dissociate user types.');
314
        }
315
316
        $this->userTypes = null;
317
318
        return $success;
319
    }
320
321
    /**
322
     * @param int $userId
323
     * @param int $organizationId
324
     * @return Query
325
     */
326
    protected function associationIdQuery(int $userId, int $organizationId): Query
327
    {
328
        return (new Query())
329
            ->select(['id'])
330
            ->from([UserAssociation::tableName()])
331
            ->where([
332
                'organizationId' => $organizationId,
333
                'userId' => $userId,
334
            ]);
335
    }
336
337
    /**
338
     * @param int $userId
339
     * @param int $organizationId
340
     * @return string|null
341
     */
342
    protected function associationId(int $userId, int $organizationId)
343
    {
344
        $id = $this->associationIdQuery($userId, $organizationId)->scalar();
345
        return is_string($id) || is_numeric($id) ? $id : null;
346
    }
347
}
348