Completed
Push — master ( b27204...a2c64d )
by Nate
05:15 queued 02:48
created

UserOrganizationsBehavior   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 405
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 15
dl 0
loc 405
ccs 0
cts 241
cp 0
rs 5.5199
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 37 1
A onAfterDelete() 0 9 1
A onAfterSave() 0 22 5
A onAfterValidate() 0 8 2
A organizationQuery() 0 18 2
A getOrganizations() 0 15 3
A setOrganizations() 0 13 2
A addOrganizations() 0 21 4
A addOrganization() 0 18 4
A resolveOrganization() 0 18 5
C saveOrganizations() 0 55 12
B associateOrganizations() 0 31 6
B dissociateOrganizations() 0 29 6
A currentAssociationQuery() 0 7 2
A resetOrganizations() 0 5 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace flipbox\organizations\behaviors;
4
5
use Craft;
6
use craft\elements\User;
7
use craft\events\ModelEvent;
8
use craft\helpers\ArrayHelper;
9
use flipbox\craft\ember\helpers\QueryHelper;
10
use flipbox\organizations\elements\Organization;
11
use flipbox\organizations\Organizations as OrganizationPlugin;
12
use flipbox\organizations\queries\OrganizationQuery;
13
use flipbox\organizations\queries\UserAssociationQuery;
14
use flipbox\organizations\records\UserAssociation;
15
use flipbox\organizations\validators\OrganizationsValidator;
16
use yii\base\Behavior;
17
use yii\base\Event;
18
use yii\base\Exception;
19
use yii\helpers\Json;
20
21
/**
22
 * Class UserOrganizationsBehavior
23
 * @package flipbox\organizations\behaviors
24
 *
25
 * @property User $owner;
26
 */
27
class UserOrganizationsBehavior extends Behavior
28
{
29
    /**
30
     * @var OrganizationQuery|null
31
     */
32
    private $organizations;
33
34
    /**
35
     * @inheritdoc
36
     */
37
    public function init()
38
    {
39
        parent::init();
40
41
        // Validate organizations
42
        Event::on(
43
            User::class,
44
            User::EVENT_AFTER_VALIDATE,
45
            function (Event $e) {
46
                /** @var User $user */
47
                $user = $e->sender;
48
                $this->onAfterValidate($user);
49
            }
50
        );
51
52
        // Associate
53
        Event::on(
54
            User::class,
55
            User::EVENT_AFTER_SAVE,
56
            function (ModelEvent $e) {
57
                /** @var User $user */
58
                $user = $e->sender;
59
                $this->onAfterSave($user);
60
            }
61
        );
62
63
        // Dissociate
64
        Event::on(
65
            User::class,
66
            User::EVENT_AFTER_DELETE,
67
            function (Event $e) {
68
                /** @var User $user */
69
                $user = $e->sender;
70
                $this->onAfterDelete($user);
71
            }
72
        );
73
    }
74
75
    /**
76
     * @param User $user
77
     * @return void
78
     * @throws \Throwable
79
     */
80
    private function onAfterDelete(User $user)
81
    {
82
        /** @var UserOrganizationsBehavior $user */
83
        // Remove organizations
84
        $user->setOrganizations([]);
85
86
        // Save associations (which is really deleting them all)
87
        $user->saveOrganizations();
88
    }
89
90
    /**
91
     * @param User|self $user
92
     * @throws Exception
93
     * @throws \Exception
94
     * @throws \Throwable
95
     * @throws \craft\errors\ElementNotFoundException
96
     */
97
    private function onAfterSave(User $user)
98
    {
99
        // Check cache for explicitly set (and possibly not saved) organizations
100
        if (null !== ($organizations = $user->getOrganizations()->getCachedResult())) {
101
102
            /** @var Organization $organization */
103
            foreach ($organizations as $organization) {
104
                if (!$organization->id) {
105
                    if (!Craft::$app->getElements()->saveElement($organization)) {
106
                        $user->addError(
107
                            'organizations',
108
                            Craft::t('organizations', 'Unable to save organization.')
109
                        );
110
111
                        throw new Exception('Unable to save organization.');
112
                    }
113
                }
114
            }
115
        }
116
117
        $this->saveOrganizations();
118
    }
119
120
    /**
121
     * @param User|self $user
122
     * @return void
123
     */
124
    private function onAfterValidate(User $user)
125
    {
126
        $error = null;
127
128
        if (!(new OrganizationsValidator())->validate($user->getOrganizations(), $error)) {
129
            $user->addError('organizations', $error);
130
        }
131
    }
132
133
    /**
134
     * @param array $criteria
135
     * @return OrganizationQuery
136
     */
137
    public function organizationQuery($criteria = []): OrganizationQuery
138
    {
139
        $query = Organization::find()
140
            ->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...
141
            ->orderBy([
142
                'organizationOrder' => SORT_ASC,
143
                'title' => SORT_ASC
144
            ]);
145
146
        if (!empty($criteria)) {
147
            QueryHelper::configure(
148
                $query,
149
                $criteria
150
            );
151
        }
152
153
        return $query;
154
    }
155
156
    /**
157
     * Get a query with associated organizations
158
     *
159
     * @param array $criteria
160
     * @return OrganizationQuery
161
     */
162
    public function getOrganizations($criteria = []): OrganizationQuery
163
    {
164
        if (null === $this->organizations) {
165
            $this->organizations = $this->organizationQuery();
166
        }
167
168
        if (!empty($criteria)) {
169
            QueryHelper::configure(
170
                $this->organizations,
171
                $criteria
172
            );
173
        }
174
175
        return $this->organizations;
176
    }
177
178
    /**
179
     * Set an array or query of organizations to a user
180
     *
181
     * @param $organizations
182
     * @return $this
183
     */
184
    public function setOrganizations($organizations)
185
    {
186
        if ($organizations instanceof OrganizationQuery) {
187
            $this->organizations = $organizations;
188
            return $this;
189
        }
190
191
        // Reset the query
192
        $this->organizations = $this->organizationQuery();
193
        $this->organizations->setCachedResult([]);
194
        $this->addOrganizations($organizations);
195
        return $this;
196
    }
197
198
    /**
199
     * Add an array of organizations to a user.  Note: This does not save the organization associations.
200
     *
201
     * @param $organizations
202
     * @return $this
203
     */
204
    protected function addOrganizations(array $organizations)
205
    {
206
        // In case a config is directly passed
207
        if (ArrayHelper::isAssociative($organizations)) {
208
            $organizations = [$organizations];
209
        }
210
211
        foreach ($organizations as $key => $organization) {
212
            if (!$organization = $this->resolveOrganization($organization)) {
213
                OrganizationPlugin::info(sprintf(
214
                    "Unable to resolve organization: %s",
215
                    (string)Json::encode($organization)
216
                ));
217
                continue;
218
            }
219
220
            $this->addOrganization($organization);
221
        }
222
223
        return $this;
224
    }
225
226
    /**
227
     * Add a organization to a user.  Note: This does not save the organization association.
228
     *
229
     * @param Organization $organization
230
     * @param bool $addToOrganization
231
     * @return $this
232
     */
233
    public function addOrganization(Organization $organization, bool $addToOrganization = true)
234
    {
235
        // Current associated organizations
236
        $allOrganizations = $this->getOrganizations()->all();
237
        $allOrganizations[] = $organization;
238
239
        $this->getOrganizations()->setCachedResult($allOrganizations);
240
241
        // Add user to organization as well?
242
        if ($addToOrganization && $organization->id !== null) {
243
            $user = $this->owner;
244
            if ($user instanceof User) {
245
                $organization->addUser($user);
246
            };
247
        }
248
249
        return $this;
250
    }
251
252
    /**
253
     * @param $organization
254
     * @return Organization
255
     */
256
    protected function resolveOrganization($organization)
257
    {
258
        if ($organization instanceof Organization) {
259
            return $organization;
260
        }
261
262
        if (is_array($organization) &&
263
            null !== ($id = ArrayHelper::getValue($organization, 'id'))
264
        ) {
265
            return Organization::findOne($id);
266
        }
267
268
        if (null !== ($object = Organization::findOne($organization))) {
269
            return $object;
270
        }
271
272
        return new Organization($organization);
273
    }
274
275
    /*******************************************
276
     * ASSOCIATE and/or DISASSOCIATE
277
     *******************************************/
278
279
    /**
280
     * @return bool
281
     * @throws \Throwable
282
     * @throws \yii\db\StaleObjectException
283
     */
284
    public function saveOrganizations(): bool
285
    {
286
        $currentAssociations = $this->currentAssociationQuery()->all();
287
288
        $success = true;
289
290
        if (null === ($records = $this->getOrganizations()->getCachedResult())) {
291
            // Delete anything that's currently set
292
            foreach ($currentAssociations as $currentAssociation) {
293
                if (!$currentAssociation->delete()) {
294
                    $success = false;
295
                }
296
            }
297
298
            if (!$success) {
299
                $this->owner->addError('types', 'Unable to dissociate organizations.');
300
            }
301
302
            return $success;
303
        }
304
305
        $associations = [];
306
        $order = 1;
307
        foreach ($records as $type) {
308
            if (null === ($association = ArrayHelper::remove($currentAssociations, $type->getId()))) {
309
                $association = (new UserAssociation())
310
                    ->setUser($this->owner)
311
                    ->setOrganization($type);
312
            }
313
314
            $association->organizationOrder = $order++;
315
316
            $associations[] = $association;
317
        }
318
319
        // Delete anything that has been removed
320
        foreach ($currentAssociations as $currentAssociation) {
321
            if (!$currentAssociation->delete()) {
322
                $success = false;
323
            }
324
        }
325
326
        // Save'em
327
        foreach ($associations as $association) {
328
            if (!$association->save()) {
329
                $success = false;
330
            }
331
        }
332
333
        if (!$success) {
334
            $this->owner->addError('organizations', 'Unable to save user organizations.');
335
        }
336
337
        return $success;
338
    }
339
340
    /**
341
     * @param OrganizationQuery $query
342
     * @return bool
343
     * @throws \Throwable
344
     */
345
    public function associateOrganizations(OrganizationQuery $query): bool
346
    {
347
        $organizations = $query->all();
348
349
        if (empty($organizations)) {
350
            return true;
351
        }
352
353
        $currentAssociations = $this->currentAssociationQuery()->all();
354
355
        $success = true;
356
        foreach ($organizations as $organization) {
357
            if (null === ($association = ArrayHelper::remove($currentAssociations, $organization->getId()))) {
358
                $association = (new UserAssociation())
359
                    ->setUser($this->owner)
360
                    ->setOrganization($organization);
361
            }
362
363
            if (!$association->save()) {
364
                $success = false;
365
            }
366
        }
367
368
        if (!$success) {
369
            $this->owner->addError('organizations', 'Unable to associate organizations.');
370
        }
371
372
        $this->resetOrganizations();
373
374
        return $success;
375
    }
376
377
    /**
378
     * @param OrganizationQuery $query
379
     * @return bool
380
     * @throws \Throwable
381
     */
382
    public function dissociateOrganizations(OrganizationQuery $query): bool
383
    {
384
        $organizations = $query->all();
385
386
        if (empty($organizations)) {
387
            return true;
388
        }
389
390
        $currentAssociations = $this->currentAssociationQuery()->all();
391
392
        $success = true;
393
        foreach ($organizations as $organization) {
394
            if (null === ($association = ArrayHelper::remove($currentAssociations, $organization->getId()))) {
395
                continue;
396
            }
397
398
            if (!$association->delete()) {
399
                $success = false;
400
            }
401
        }
402
403
        if (!$success) {
404
            $this->owner->addError('organizations', 'Unable to associate organizations.');
405
        }
406
407
        $this->resetOrganizations();
408
409
        return $success;
410
    }
411
412
    /**
413
     * @return UserAssociationQuery
414
     */
415
    protected function currentAssociationQuery(): UserAssociationQuery
416
    {
417
        return UserAssociation::find()
418
            ->userId($this->owner->getId() ?: false)
419
            ->indexBy('organizationId')
420
            ->orderBy(['organizationOrder' => SORT_ASC]);
421
    }
422
423
    /**
424
     * @return User
425
     */
426
    public function resetOrganizations()
427
    {
428
        $this->organizations = null;
429
        return $this->owner;
430
    }
431
}
432