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

TypesAttributeTrait::saveTypes()   C

Complexity

Conditions 13
Paths 60

Size

Total Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
dl 0
loc 57
ccs 0
cts 44
cp 0
rs 6.6166
c 0
b 0
f 0
cc 13
nc 60
nop 0
crap 182

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\elements;
10
11
use Craft;
12
use craft\helpers\ArrayHelper;
13
use flipbox\craft\ember\helpers\QueryHelper;
14
use flipbox\organizations\queries\OrganizationTypeQuery;
15
use flipbox\organizations\records\OrganizationType;
16
use flipbox\organizations\records\OrganizationType as TypeModel;
17
use flipbox\organizations\records\OrganizationTypeAssociation;
18
19
/**
20
 * @author Flipbox Factory <[email protected]>
21
 * @since 1.0.0
22
 */
23
trait TypesAttributeTrait
24
{
25
    /**
26
     * @var OrganizationTypeQuery
27
     */
28
    private $types;
29
30
    /**
31
     * @var TypeModel|false
32
     */
33
    private $activeType;
34
35
    /************************************************************
36
     * REQUEST
37
     ************************************************************/
38
39
    /**
40
     * AssociateUserToOrganization an array of types from request input
41
     *
42
     * @param string $identifier
43
     * @return $this
44
     */
45
    public function setTypesFromRequest(string $identifier = 'types')
46
    {
47
        if ($types = Craft::$app->getRequest()->getBodyParam($identifier, [])) {
48
            $this->setTypes($types);
49
        }
50
51
        return $this;
52
    }
53
54
    /************************************************************
55
     * ACTIVE TYPE
56
     ************************************************************/
57
58
    /**
59
     * @param TypeModel|null $type
60
     * @return $this
61
     */
62
    public function setActiveType(TypeModel $type = null)
63
    {
64
        if ($type) {
65
            $this->addType($type);
66
        }
67
68
        $this->activeType = (null === $type) ? false : $type;
69
        return $this;
70
    }
71
72
    /**
73
     * @return TypeModel|null
74
     */
75
    public function getActiveType()
76
    {
77
        if (null === $this->activeType) {
78
            if (!$activeType = $this->getPrimaryType()) {
79
                $activeType = false;
80
            }
81
82
            $this->activeType = $activeType;
83
        }
84
85
        return (false === $this->activeType) ? null : $this->activeType;
86
    }
87
88
    /************************************************************
89
     * TYPES
90
     ************************************************************/
91
92
    /**
93
     * Get an array of types associated to an organization
94
     *
95
     * @param array $criteria
96
     * @return OrganizationTypeQuery
97
     */
98
    public function getTypes($criteria = [])
99
    {
100
        if (null === $this->types) {
101
            $this->types = OrganizationType::find()
102
                ->organization($this);
103
        }
104
105
        if (!empty($criteria)) {
106
            QueryHelper::configure(
107
                $this->types,
108
                $criteria
109
            );
110
        }
111
112
        return $this->types;
113
    }
114
115
    /**
116
     * AssociateUserToOrganization types to an organization
117
     *
118
     * @param $types
119
     * @return $this
120
     */
121
    public function setTypes($types)
122
    {
123
        if ($types instanceof OrganizationTypeQuery) {
124
            $this->types = $types;
125
            return $this;
126
        }
127
128
        // Reset the query
129
        $this->types = OrganizationType::find()
130
            ->organization($this);
131
132
        // Remove all types
133
        $this->types->setCachedResult([]);
134
135
        if (!empty($types)) {
136
            if (!is_array($types)) {
137
                $types = [$types];
138
            }
139
140
            $this->addTypes($types);
141
        }
142
143
        return $this;
144
    }
145
146
    /**
147
     * AssociateUserToOrganization an array of types to an organization
148
     *
149
     * @param $types
150
     * @return $this
151
     */
152
    public function addTypes(array $types)
153
    {
154
        // In case a config is directly passed
155
        if (ArrayHelper::isAssociative($types)) {
156
            $types = [$types];
157
        }
158
159
        foreach ($types as $key => $type) {
160
            // Ensure we have a model
161
            if (!$type instanceof OrganizationType) {
162
                $type = $this->resolveType($type);
163
            }
164
165
            $this->addType($type);
166
        }
167
168
        return $this;
169
    }
170
171
    /**
172
     * AssociateUserToOrganization a type to an organization
173
     *
174
     * @param OrganizationType $type
175
     * @return $this
176
     */
177
    public function addType(OrganizationType $type)
178
    {
179
        $currentTypes = $this->getTypes()->all();
180
181
        $indexedTypes = ArrayHelper::index(
182
            $currentTypes,
183
            'handle'
184
        );
185
186
        if (!array_key_exists($type->handle, $indexedTypes)) {
187
            $currentTypes[] = $type;
188
            $this->getTypes()->setCachedResult($currentTypes);
189
        }
190
191
        return $this;
192
    }
193
194
    /**
195
     * DissociateUserFromOrganization a type from an organization
196
     *
197
     * @param array $types
198
     * @return $this
199
     */
200
    public function removeTypes(array $types)
201
    {
202
        // In case a config is directly passed
203
        if (ArrayHelper::isAssociative($types)) {
204
            $types = [$types];
205
        }
206
207
        foreach ($types as $key => $type) {
208
            if (!$type instanceof OrganizationType) {
209
                $type = $this->resolveType($type);
210
            }
211
212
            $this->removeType($type);
213
        }
214
215
        return $this;
216
    }
217
218
    /**
219
     * @param mixed $type
220
     * @return OrganizationType
221
     */
222
    protected function resolveType($type): OrganizationType
223
    {
224
        if (null !== ($type = OrganizationType::findOne($type))) {
225
            return $type;
226
        }
227
228
        if (!is_array($type)) {
229
            $type = ArrayHelper::toArray($type, [], false);
230
        }
231
232
        return new OrganizationType($type);
233
    }
234
235
    /**
236
     * DissociateUserFromOrganization a type from an organization
237
     *
238
     * @param OrganizationType $type
239
     * @return $this
240
     */
241
    public function removeType(OrganizationType $type)
242
    {
243
        $indexedTypes = ArrayHelper::index(
244
            $this->getTypes()->all(),
245
            'handle'
246
        );
247
248
        // Does the type already exist?
249
        if (array_key_exists($type->handle, $indexedTypes)) {
250
            unset($indexedTypes[$type->handle]);
251
252
            $this->getTypes()->setCachedResult(
253
                array_values($indexedTypes)
254
            );
255
        }
256
257
        return $this;
258
    }
259
260
    /**
261
     * Reset types
262
     *
263
     * @return $this
264
     */
265
    public function resetTypes()
266
    {
267
        $this->types = null;
268
        return $this;
269
    }
270
271
    /**
272
     * Get an associated type by identifier (id/handle)
273
     *
274
     * @param $identifier
275
     * @return null|TypeModel
276
     */
277
    public function getType($identifier)
278
    {
279
        // Determine index type
280
        $indexBy = (is_numeric($identifier)) ? 'id' : 'handle';
281
282
        // Find all types
283
        $allTypes = ArrayHelper::index(
284
            $this->getTypes()->all(),
285
            $indexBy
286
        );
287
288
        return array_key_exists($identifier, $allTypes) ? $allTypes[$identifier] : null;
289
    }
290
291
    /**
292
     * Identify whether a type is associated to the element
293
     *
294
     * @param TypeModel|null $type
295
     * @return bool
296
     */
297
    public function hasType(TypeModel $type = null): bool
298
    {
299
        if (null === $type) {
300
            return !empty($this->getTypes());
301
        }
302
303
        return null !== $this->getType($type->id);
304
    }
305
306
307
    /************************************************************
308
     * PRIMARY TYPE
309
     ************************************************************/
310
311
    /**
312
     * Identify whether a primary type is set
313
     *
314
     * @return bool
315
     */
316
    public function hasPrimaryType()
317
    {
318
        return $this->getTypes()->one() instanceof TypeModel;
319
    }
320
321
    /**
322
     * Identify whether the type is primary
323
     *
324
     * @param $type
325
     * @return bool
326
     */
327
    public function isPrimaryType(TypeModel $type)
328
    {
329
        if ($primaryType = $this->getPrimaryType()) {
330
            return $primaryType->id === $type->id;
331
        }
332
333
        return false;
334
    }
335
336
    /**
337
     * @param TypeModel $type
338
     * @return $this
339
     */
340
    public function setPrimaryType(TypeModel $type = null)
341
    {
342
        if (null === $type) {
343
            return $this;
344
        }
345
346
        return $this->setTypes(
347
            array_merge(
348
                [
349
                    $type
350
                ],
351
                $this->getTypes()->all()
352
            )
353
        );
354
    }
355
356
    /**
357
     * Get the primary type
358
     *
359
     * @return TypeModel|null
360
     */
361
    public function getPrimaryType()
362
    {
363
        if (!$this->hasPrimaryType()) {
364
            return null;
365
        }
366
367
        return $this->getTypes()->one();
368
    }
369
370
    /*******************************************
371
     * ASSOCIATE and/or DISASSOCIATE
372
     *******************************************/
373
374
    /**
375
     * @return bool
376
     * @throws \Throwable
377
     * @throws \flipbox\craft\ember\exceptions\RecordNotFoundException
378
     * @throws \yii\db\StaleObjectException
379
     */
380
    public function saveTypes(): bool
381
    {
382
        $currentAssociations = OrganizationTypeAssociation::find()
383
            ->organizationId($this->getId() ?: false)
0 ignored issues
show
Bug introduced by
It seems like getId() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
384
            ->indexBy('typeId')
385
            ->all();
386
387
        $success = true;
388
389
        // DeleteOrganization
390
        if (null === ($types = $this->getTypes()->getCachedResult())) {
391
            foreach ($currentAssociations as $currentAssociation) {
392
                if (!$currentAssociation->delete()) {
393
                    $success = false;
394
                }
395
            }
396
397
            if (!$success) {
398
                $this->addError('types', 'Unable to dissociate types.');
0 ignored issues
show
Bug introduced by
It seems like addError() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
399
            }
400
401
            return $success;
402
        }
403
404
        $associations = [];
405
        $order = 1;
406
        foreach ($types as $type) {
407
            if (null === ($association = ArrayHelper::remove($currentAssociations, $type->getId()))) {
408
                $association = (new OrganizationTypeAssociation())
409
                    ->setType($type)
410
                    ->setOrganization($this);
411
            }
412
413
            $association->sortOrder = $order++;
414
415
            $associations[] = $association;
416
        }
417
418
        // DeleteOrganization those removed
419
        foreach ($currentAssociations as $currentAssociation) {
420
            if (!$currentAssociation->delete()) {
421
                $success = false;
422
            }
423
        }
424
425
        foreach ($associations as $association) {
426
            if (!$association->save()) {
427
                $success = false;
428
            }
429
        }
430
431
        if (!$success) {
432
            $this->addError('types', 'Unable to associate types.');
0 ignored issues
show
Bug introduced by
It seems like addError() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
433
        }
434
435
        return $success;
436
    }
437
438
    /**
439
     * @param OrganizationTypeQuery $query
440
     * @return bool
441
     * @throws \Throwable
442
     */
443
    public function associateTypes(OrganizationTypeQuery $query): bool
444
    {
445
        $types = $query->all();
446
447
        if (empty($types)) {
448
            return true;
449
        }
450
451
        $currentAssociations = $currentAssociations = OrganizationTypeAssociation::find()
452
            ->organizationId($this->getId() ?: false)
0 ignored issues
show
Bug introduced by
It seems like getId() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
453
            ->indexBy('typeId')
454
            ->all();
455
456
        $success = true;
457
        foreach ($types as $type) {
458
            if (null === ($association = ArrayHelper::remove($currentAssociations, $type->getId()))) {
459
                $association = (new OrganizationTypeAssociation())
460
                    ->setType($type)
461
                    ->setOrganization($this);
462
            }
463
464
            if (!$association->save()) {
465
                $success = false;
466
            }
467
        }
468
469
        if (!$success) {
470
            $this->addError('organizations', 'Unable to associate types.');
0 ignored issues
show
Bug introduced by
It seems like addError() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
471
        }
472
473
        $this->resetTypes();
474
475
        return $success;
476
    }
477
478
    /**
479
     * @param OrganizationTypeQuery $query
480
     * @return bool
481
     */
482
    public function dissociateTypes(OrganizationTypeQuery $query): bool
483
    {
484
        $types = $query->all();
485
486
        if (empty($types)) {
487
            return true;
488
        }
489
490
        $currentAssociations = $currentAssociations = OrganizationTypeAssociation::find()
491
            ->organizationId($this->getId() ?: false)
0 ignored issues
show
Bug introduced by
It seems like getId() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
492
            ->indexBy('typeId')
493
            ->all();
494
495
        $success = true;
496
        foreach ($types as $type) {
497
            if (null === ($association = ArrayHelper::remove($currentAssociations, $type->getId()))) {
498
                continue;
499
            }
500
501
            if (!$association->delete()) {
502
                $success = false;
503
            }
504
        }
505
506
        if (!$success) {
507
            $this->addError('organizations', 'Unable to dissociate types.');
0 ignored issues
show
Bug introduced by
It seems like addError() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
508
        }
509
510
        $this->resetTypes();
511
512
        return $success;
513
    }
514
}
515