Completed
Push — develop ( 27afa3...8e20cf )
by Nate
06:58
created

Provider   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 541
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 15

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 57
lcom 3
cbo 15
dl 0
loc 541
ccs 0
cts 306
cp 0
rs 5.04
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
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 upsertInternal() 0 12 3
B saveInstances() 0 40 7
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 getIcon() 0 10 2
A saveAndLock() 0 8 2
A addLock() 0 15 2
A removeLock() 0 15 4
A updateInternal() 0 8 3

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
     */
269
    public function saveAndLock(PluginInterface $plugin, $runValidation = true, $attributeNames = null): bool
270
    {
271
        if (!$this->save($runValidation, $attributeNames)) {
272
            return false;
273
        }
274
275
        return $this->addLock($plugin);
276
    }
277
278
279
    /*******************************************
280
     * LOCK
281
     *******************************************/
282
283
    /**
284
     * @param PluginInterface $plugin
285
     * @return bool
286
     */
287
    public function addLock(PluginInterface $plugin): bool
288
    {
289
        if (null === ($pluginId = $this->getPluginId($plugin))) {
290
            return false;
291
        }
292
293
        $record = new ProviderLock();
294
295
        $record->setAttributes([
296
            'providerId' => $this->getId(),
297
            'pluginId' => $pluginId
298
        ]);
299
300
        return (bool)$record->save();
301
    }
302
303
    /**
304
     * @param PluginInterface $plugin
305
     * @return bool
306
     * @throws \Throwable
307
     */
308
    public function removeLock(PluginInterface $plugin): bool
309
    {
310
        if (null === ($pluginId = $this->getPluginId($plugin))) {
311
            return false;
312
        }
313
314
        if (null === ($record = ProviderLock::findOne([
315
                'providerId' => $this->getId() ?: 0,
316
                'pluginId' => $pluginId
317
            ]))) {
318
            return true;
319
        }
320
321
        return (bool)$record->delete();
322
    }
323
324
    /**
325
     * @return bool
326
     */
327
    public function isLocked(): bool
328
    {
329
        return !empty($this->locks);
330
    }
331
332
    /**
333
     * @param PluginInterface $plugin
334
     * @return int|null
335
     */
336
    protected function getPluginId(PluginInterface $plugin)
337
    {
338
        $id = (new Query())
339
            ->select([
340
                'id',
341
            ])
342
            ->from(['{{%plugins}}'])
343
            ->where([
344
                'handle' => $plugin->getHandle()
345
            ])
346
            ->scalar();
347
348
        return $id ? (int)$id : null;
349
    }
350
351
    /**
352
     * @param PluginInterface $plugin
353
     * @return int|null
354
     */
355
    protected function getPluginName(PluginInterface $plugin)
356
    {
357
        $id = (new Query())
358
            ->select([
359
                'id',
360
            ])
361
            ->from(['{{%plugins}}'])
362
            ->where([
363
                'handle' => $plugin->getHandle()
364
            ])
365
            ->scalar();
366
367
        return $id ? (int)$id : null;
368
    }
369
370
371
    /*******************************************
372
     * DELETE
373
     *******************************************/
374
375
    /**
376
     * @param PluginInterface|null $plugin
377
     * @return bool|false|int
378
     * @throws \Throwable
379
     * @throws \yii\db\StaleObjectException
380
     */
381
    public function delete(PluginInterface $plugin = null)
382
    {
383
        return $this->canDelete($plugin) ? parent::delete() : false;
384
    }
385
386
    /**
387
     * @param PluginInterface|null $plugin
388
     * @return bool
389
     * @throws \craft\errors\InvalidPluginException
390
     */
391
    protected function canDelete(PluginInterface $plugin = null)
392
    {
393
        // If a plugin is locking this, prevent deletion
394
        $lockQuery = $this->getLocks();
395
        if (null !== $plugin) {
396
            $lockQuery->andWhere(
397
                ['<>', 'pluginId', $this->getPluginId($plugin)]
398
            );
399
        }
400
401
        $locks = $lockQuery->all();
402
403
        if (count($locks) > 0) {
404
            $handles = (new Query())
405
                ->select([
406
                    'handle',
407
                ])
408
                ->from(['{{%plugins}}'])
409
                ->where([
410
                    'id' => ArrayHelper::getColumn($locks, 'pluginId'),
411
                ])
412
                ->column();
413
414
            $names = [];
415
            foreach ($handles as $handle) {
416
                $plugin = Craft::$app->getPlugins()->getPluginInfo($handle);
417
                $names[] = $plugin['name'] ?? 'Unknown Plugin';
418
            }
419
420
            $this->addError(
421
                'locks',
422
                Craft::t(
423
                    'patron',
424
                    'The provider is locked by the following plugins: {plugins}',
425
                    [
426
                        'plugins' => StringHelper::toString($names, ', ')
427
                    ]
428
                )
429
            );
430
            return false;
431
        }
432
433
        return true;
434
    }
435
436
    /*******************************************
437
     * EVENTS
438
     *******************************************/
439
440
    /**
441
     * @inheritdoc
442
     */
443
    public function beforeSave($insert): bool
444
    {
445
        if (!parent::beforeSave($insert)) {
446
            return false;
447
        }
448
449
        if ($insert !== true ||
450
            $this->isRelationPopulated('instances') !== true ||
451
            $this->autoSaveInstances !== true
452
        ) {
453
            return true;
454
        }
455
456
        $this->insertInstances = $this->instances;
457
458
        return true;
459
    }
460
461
    /*******************************************
462
     * UPDATE / INSERT
463
     *******************************************/
464
465
    /**
466
     * We're extracting the environments that may have been explicitly set on the record.  When the 'id'
467
     * attribute is updated, it removes any associated relationships.
468
     *
469
     * @inheritdoc
470
     * @throws \Throwable
471
     */
472
    protected function insertInternal($attributes = null)
473
    {
474
        if (!parent::insertInternal($attributes)) {
475
            return false;
476
        }
477
478
        if (null === $this->insertInstances) {
479
            return true;
480
        }
481
482
        $this->setInstances($this->insertInstances);
483
        $this->insertInstances = null;
484
485
        return $this->upsertInternal($attributes);
486
    }
487
488
    /**
489
     * @inheritdoc
490
     * @throws \Throwable
491
     */
492
    protected function updateInternal($attributes = null)
493
    {
494
        if (false === ($response = parent::updateInternal($attributes))) {
495
            return false;
496
        }
497
498
        return $this->upsertInternal($attributes) ? $response : false;
499
    }
500
501
    /**
502
     * @param null $attributes
503
     * @return bool
504
     * @throws \Throwable
505
     * @throws \yii\db\StaleObjectException
506
     */
507
    protected function upsertInternal($attributes = null): bool
508
    {
509
        if (empty($attributes)) {
510
            return $this->saveInstances();
511
        }
512
513
        if (array_key_exists('instances', $attributes)) {
514
            return $this->saveInstances(true);
515
        }
516
517
        return true;
518
    }
519
520
    /**
521
     * @param bool $force
522
     * @return bool
523
     * @throws \Throwable
524
     * @throws \yii\db\StaleObjectException
525
     */
526
    protected function saveInstances(bool $force = false): bool
527
    {
528
        if ($force === false && $this->autoSaveInstances !== true) {
529
            return true;
530
        }
531
532
        $successful = true;
533
534
        /** @var ProviderInstance[] $allRecords */
535
        $allRecords = $this->getInstances()
536
            ->all();
537
538
        ArrayHelper::index($allRecords, 'providerId');
539
540
        foreach ($this->instances as $model) {
541
            ArrayHelper::remove($allRecords, $this->getId());
542
            $model->providerId = $this->getId();
543
544
            if (!$model->save()) {
545
                $successful = false;
546
                // Log the errors
547
                $error = Craft::t(
548
                    'patron',
549
                    "Couldn't save instance due to validation errors:"
550
                );
551
                foreach ($model->getFirstErrors() as $attributeError) {
552
                    $error .= "\n- " . Craft::t('patron', $attributeError);
553
                }
554
555
                $this->addError('instances', $error);
556
            }
557
        }
558
559
        // Delete old records
560
        foreach ($allRecords as $record) {
561
            $record->delete();
562
        }
563
564
        return $successful;
565
    }
566
567
    /**
568
     * @return string
569
     * @throws \ReflectionException
570
     */
571
    public function getDisplayName(): string
572
    {
573
        return ProviderHelper::displayName(
574
            $this->class
575
        );
576
    }
577
}
578