Completed
Push — develop ( edb8bf...6f43a0 )
by Nate
08:26
created

Provider   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 550
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 15

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 58
lcom 3
cbo 15
dl 0
loc 550
ccs 0
cts 301
cp 0
rs 4.5599
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A rules() 0 32 1
A getTokens() 0 16 2
A getLocks() 0 16 2
A getInstances() 0 17 2
A setInstances() 0 10 2
A resolveInstance() 0 14 2
A getEnvironments() 0 18 2
A saveAndLock() 0 8 2
A getIcon() 0 10 2
A addLock() 0 15 2
A removeLock() 0 15 4
A isLocked() 0 4 1
A getPluginId() 0 14 2
A getPluginName() 0 14 2
A delete() 0 4 2
A canDelete() 0 44 4
A beforeSave() 0 17 5
A insertInternal() 0 15 3
A updateInternal() 0 8 3
A upsertInternal() 0 12 3
B saveInstances() 0 40 7
A __toString() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Provider 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 Provider, 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/patron/license
6
 * @link       https://www.flipboxfactory.com/software/patron/
7
 */
8
9
namespace flipbox\patron\records;
10
11
use Craft;
12
use craft\base\PluginInterface;
13
use craft\db\Query;
14
use craft\helpers\StringHelper;
15
use flipbox\craft\ember\helpers\ModelHelper;
16
use flipbox\craft\ember\helpers\ObjectHelper;
17
use flipbox\craft\ember\helpers\QueryHelper;
18
use flipbox\craft\ember\models\HandleRulesTrait;
19
use flipbox\craft\ember\records\ActiveRecordWithId;
20
use flipbox\craft\ember\records\StateAttributeTrait;
21
use flipbox\patron\helpers\ProviderHelper;
22
use flipbox\patron\Patron;
23
use flipbox\patron\queries\ProviderActiveQuery;
24
use flipbox\patron\validators\ProviderValidator;
25
use yii\helpers\ArrayHelper;
26
27
/**
28
 * @author Flipbox Factory <[email protected]>
29
 * @since 1.0.0
30
 *
31
 * @property string $class
32
 * @property ProviderLock[] $locks
33
 * @property Token[] $tokens
34
 * @property ProviderInstance[] $instances
35
 * @property ProviderEnvironment[] $environments
36
 */
37
class Provider extends ActiveRecordWithId
38
{
39
    use HandleRulesTrait,
40
        StateAttributeTrait;
41
42
    /**
43
     * The table alias
44
     */
45
    const TABLE_ALIAS = 'patron_providers';
46
47
    /**
48
     * @deprecated
49
     */
50
    const CLIENT_ID_LENGTH = ProviderInstance::CLIENT_ID_LENGTH;
51
52
    /**
53
     * @deprecated
54
     */
55
    const CLIENT_SECRET_LENGTH = ProviderInstance::CLIENT_SECRET_LENGTH;
56
57
    /**
58
     * @var bool
59
     */
60
    public $autoSaveInstances = false;
61
62
    /**
63
     * Environments that are temporarily set during the save process
64
     *
65
     * @var null|array
66
     */
67
    private $insertInstances;
68
69
    /**
70
     * @inheritdoc
71
     * @return ProviderActiveQuery
72
     * @throws \yii\base\InvalidConfigException
73
     */
74
    public static function find()
75
    {
76
        /** @noinspection PhpIncompatibleReturnTypeInspection */
77
        return Craft::createObject(ProviderActiveQuery::class, [get_called_class()]);
78
    }
79
80
    /**
81
     * @return string|null
82
     */
83
    public function getIcon()
84
    {
85
        if ($this->class === null) {
86
            return null;
87
        }
88
89
        return Patron::getInstance()->getCp()->getProviderIcon(
90
            $this->class
91
        );
92
    }
93
94
    /**
95
     * @inheritdoc
96
     */
97
    public function rules()
98
    {
99
        return array_merge(
100
            parent::rules(),
101
            $this->handleRules(),
102
            $this->stateRules(),
103
            [
104
                [
105
                    [
106
                        'class'
107
                    ],
108
                    ProviderValidator::class
109
                ],
110
                [
111
                    [
112
                        'class'
113
                    ],
114
                    'required'
115
                ],
116
                [
117
                    [
118
                        'class',
119
                        'settings'
120
                    ],
121
                    'safe',
122
                    'on' => [
123
                        ModelHelper::SCENARIO_DEFAULT
124
                    ]
125
                ]
126
            ]
127
        );
128
    }
129
130
    /**
131
     * Get all of the associated tokens.
132
     *
133
     * @param array $config
134
     * @return \yii\db\ActiveQuery
135
     */
136
    public function getTokens(array $config = [])
137
    {
138
        $query = $this->hasMany(
139
            Token::class,
140
            ['providerId' => 'id']
141
        );
142
143
        if (!empty($config)) {
144
            QueryHelper::configure(
145
                $query,
146
                $config
147
            );
148
        }
149
150
        return $query;
151
    }
152
153
    /**
154
     * Get all of the associated tokens.
155
     *
156
     * @param array $config
157
     * @return \yii\db\ActiveQuery
158
     */
159
    public function getLocks(array $config = [])
160
    {
161
        $query = $this->hasMany(
162
            ProviderLock::class,
163
            ['providerId' => 'id']
164
        );
165
166
        if (!empty($config)) {
167
            QueryHelper::configure(
168
                $query,
169
                $config
170
            );
171
        }
172
173
        return $query;
174
    }
175
176
    /**
177
     * Get all of the associated instances.
178
     *
179
     * @param array $config
180
     * @return \yii\db\ActiveQuery
181
     */
182
    public function getInstances(array $config = [])
183
    {
184
        $query = $this->hasMany(
185
            ProviderInstance::class,
186
            ['providerId' => 'id']
187
        )
188
            ->indexBy('id');
189
190
        if (!empty($config)) {
191
            QueryHelper::configure(
192
                $query,
193
                $config
194
            );
195
        }
196
197
        return $query;
198
    }
199
200
    /**
201
     * @param array $instances
202
     * @return $this
203
     */
204
    public function setInstances(array $instances = [])
205
    {
206
        $records = [];
207
        foreach (array_filter($instances) as $environment) {
208
            $records[] = $this->resolveInstance($environment);
209
        }
210
211
        $this->populateRelation('instances', $records);
212
        return $this;
213
    }
214
215
    /**
216
     * @param $instance
217
     * @return ProviderInstance
218
     */
219
    protected function resolveInstance($instance): ProviderInstance
220
    {
221
        if ($instance instanceof ProviderInstance) {
222
            return $instance;
223
        }
224
225
        $record = new ProviderInstance();
226
227
        /** @noinspection PhpIncompatibleReturnTypeInspection */
228
        return ObjectHelper::populate(
229
            $record,
230
            $instance
231
        );
232
    }
233
234
    /**
235
     * Get all of the associated environments.
236
     *
237
     * @param array $config
238
     * @return \yii\db\ActiveQuery
239
     */
240
    public function getEnvironments(array $config = [])
241
    {
242
        $query = $this->hasMany(
243
            ProviderEnvironment::class,
244
            ['instanceId' => 'id']
245
        )
246
            ->via('instances')
247
            ->indexBy('environment');
248
249
        if (!empty($config)) {
250
            QueryHelper::configure(
251
                $query,
252
                $config
253
            );
254
        }
255
256
        return $query;
257
    }
258
259
    /*******************************************
260
     * SAVE
261
     *******************************************/
262
263
    /**
264
     * @param PluginInterface $plugin
265
     * @param bool $runValidation
266
     * @param null $attributeNames
267
     * @return bool
268
     * @throws \Throwable
269
     * @throws \yii\db\StaleObjectException
270
     */
271
    public function saveAndLock(PluginInterface $plugin, $runValidation = true, $attributeNames = null): bool
272
    {
273
        if (!$this->save($runValidation, $attributeNames)) {
274
            return false;
275
        }
276
277
        return $this->addLock($plugin);
278
    }
279
280
281
    /*******************************************
282
     * LOCK
283
     *******************************************/
284
285
    /**
286
     * @param PluginInterface $plugin
287
     * @return bool
288
     */
289
    public function addLock(PluginInterface $plugin): bool
290
    {
291
        if (null === ($pluginId = $this->getPluginId($plugin))) {
292
            return false;
293
        }
294
295
        $record = new ProviderLock();
296
297
        $record->setAttributes([
298
            'providerId' => $this->getId(),
299
            'pluginId' => $pluginId
300
        ]);
301
302
        return (bool)$record->save();
303
    }
304
305
    /**
306
     * @param PluginInterface $plugin
307
     * @return bool
308
     * @throws \Throwable
309
     */
310
    public function removeLock(PluginInterface $plugin): bool
311
    {
312
        if (null === ($pluginId = $this->getPluginId($plugin))) {
313
            return false;
314
        }
315
316
        if (null === ($record = ProviderLock::findOne([
317
                'providerId' => $this->getId() ?: 0,
318
                'pluginId' => $pluginId
319
            ]))) {
320
            return true;
321
        }
322
323
        return (bool)$record->delete();
324
    }
325
326
    /**
327
     * @return bool
328
     */
329
    public function isLocked(): bool
330
    {
331
        return !empty($this->locks);
332
    }
333
334
    /**
335
     * @param PluginInterface $plugin
336
     * @return int|null
337
     */
338
    protected function getPluginId(PluginInterface $plugin)
339
    {
340
        $id = (new Query())
341
            ->select([
342
                'id',
343
            ])
344
            ->from(['{{%plugins}}'])
345
            ->where([
346
                'handle' => $plugin->getHandle()
347
            ])
348
            ->scalar();
349
350
        return $id ? (int)$id : null;
351
    }
352
353
    /**
354
     * @param PluginInterface $plugin
355
     * @return int|null
356
     */
357
    protected function getPluginName(PluginInterface $plugin)
358
    {
359
        $id = (new Query())
360
            ->select([
361
                'id',
362
            ])
363
            ->from(['{{%plugins}}'])
364
            ->where([
365
                'handle' => $plugin->getHandle()
366
            ])
367
            ->scalar();
368
369
        return $id ? (int)$id : null;
370
    }
371
372
373
    /*******************************************
374
     * DELETE
375
     *******************************************/
376
377
    /**
378
     * @param PluginInterface|null $plugin
379
     * @return bool|false|int
380
     * @throws \Throwable
381
     * @throws \yii\db\StaleObjectException
382
     */
383
    public function delete(PluginInterface $plugin = null)
384
    {
385
        return $this->canDelete($plugin) ? parent::delete() : false;
386
    }
387
388
    /**
389
     * @param PluginInterface|null $plugin
390
     * @return bool
391
     * @throws \craft\errors\InvalidPluginException
392
     */
393
    protected function canDelete(PluginInterface $plugin = null)
394
    {
395
        // If a plugin is locking this, prevent deletion
396
        $lockQuery = $this->getLocks();
397
        if (null !== $plugin) {
398
            $lockQuery->andWhere(
399
                ['<>', 'pluginId', $this->getPluginId($plugin)]
400
            );
401
        }
402
403
        $locks = $lockQuery->all();
404
405
        if (count($locks) > 0) {
406
            $handles = (new Query())
407
                ->select([
408
                    'handle',
409
                ])
410
                ->from(['{{%plugins}}'])
411
                ->where([
412
                    'id' => ArrayHelper::getColumn($locks, 'pluginId'),
413
                ])
414
                ->column();
415
416
            $names = [];
417
            foreach ($handles as $handle) {
418
                $plugin = Craft::$app->getPlugins()->getPluginInfo($handle);
419
                $names[] = $plugin['name'] ?? 'Unknown Plugin';
420
            }
421
422
            $this->addError(
423
                'locks',
424
                Craft::t(
425
                    'patron',
426
                    'The provider is locked by the following plugins: {plugins}',
427
                    [
428
                        'plugins' => StringHelper::toString($names, ', ')
429
                    ]
430
                )
431
            );
432
            return false;
433
        }
434
435
        return true;
436
    }
437
438
    /*******************************************
439
     * EVENTS
440
     *******************************************/
441
442
    /**
443
     * @inheritdoc
444
     */
445
    public function beforeSave($insert): bool
446
    {
447
        if (!parent::beforeSave($insert)) {
448
            return false;
449
        }
450
451
        if ($insert !== true ||
452
            $this->isRelationPopulated('instances') !== true ||
453
            $this->autoSaveInstances !== true
454
        ) {
455
            return true;
456
        }
457
458
        $this->insertInstances = $this->instances;
459
460
        return true;
461
    }
462
463
    /*******************************************
464
     * UPDATE / INSERT
465
     *******************************************/
466
467
    /**
468
     * We're extracting the environments that may have been explicitly set on the record.  When the 'id'
469
     * attribute is updated, it removes any associated relationships.
470
     *
471
     * @inheritdoc
472
     * @throws \Throwable
473
     */
474
    protected function insertInternal($attributes = null)
475
    {
476
        if (!parent::insertInternal($attributes)) {
477
            return false;
478
        }
479
480
        if (null === $this->insertInstances) {
481
            return true;
482
        }
483
484
        $this->setInstances($this->insertInstances);
485
        $this->insertInstances = null;
486
487
        return $this->upsertInternal($attributes);
488
    }
489
490
    /**
491
     * @inheritdoc
492
     * @throws \Throwable
493
     */
494
    protected function updateInternal($attributes = null)
495
    {
496
        if (false === ($response = parent::updateInternal($attributes))) {
497
            return false;
498
        }
499
500
        return $this->upsertInternal($attributes) ? $response : false;
501
    }
502
503
    /**
504
     * @param null $attributes
505
     * @return bool
506
     * @throws \Throwable
507
     * @throws \yii\db\StaleObjectException
508
     */
509
    protected function upsertInternal($attributes = null): bool
510
    {
511
        if (empty($attributes)) {
512
            return $this->saveInstances();
513
        }
514
515
        if (array_key_exists('instances', $attributes)) {
516
            return $this->saveInstances(true);
517
        }
518
519
        return true;
520
    }
521
522
    /**
523
     * @param bool $force
524
     * @return bool
525
     * @throws \Throwable
526
     * @throws \yii\db\StaleObjectException
527
     */
528
    protected function saveInstances(bool $force = false): bool
529
    {
530
        if ($force === false && $this->autoSaveInstances !== true) {
531
            return true;
532
        }
533
534
        $successful = true;
535
536
        /** @var ProviderInstance[] $allRecords */
537
        $allRecords = $this->getInstances()
538
            ->all();
539
540
        ArrayHelper::index($allRecords, 'providerId');
541
542
        foreach ($this->instances as $model) {
543
            ArrayHelper::remove($allRecords, $this->getId());
544
            $model->providerId = $this->getId();
545
546
            if (!$model->save()) {
547
                $successful = false;
548
                // Log the errors
549
                $error = Craft::t(
550
                    'patron',
551
                    "Couldn't save instance due to validation errors:"
552
                );
553
                foreach ($model->getFirstErrors() as $attributeError) {
554
                    $error .= "\n- " . Craft::t('patron', $attributeError);
555
                }
556
557
                $this->addError('instances', $error);
558
            }
559
        }
560
561
        // Delete old records
562
        foreach ($allRecords as $record) {
563
            $record->delete();
564
        }
565
566
        return $successful;
567
    }
568
569
    /**
570
     * @return string
571
     */
572
    public function getDisplayName(): string
573
    {
574
        return ProviderHelper::displayName(
575
            $this->class
576
        );
577
    }
578
579
    /**
580
     * @return string
581
     */
582
    public function __toString()
583
    {
584
        return $this->getDisplayName();
585
    }
586
}
587