GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

DefaultConfiguration   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 471
Duplicated Lines 2.55 %

Coupling/Cohesion

Components 2
Dependencies 8

Test Coverage

Coverage 0%
Metric Value
wmc 49
lcom 2
cbo 8
dl 12
loc 471
ccs 0
cts 221
cp 0
rs 8.5454

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A initDefaultPathsToConfig() 0 4 1
A setLayout() 0 3 1
A getLayout() 0 4 1
A isModifiable() 0 4 1
A getName() 0 4 1
A getInstance() 0 10 2
A isInitialized() 0 4 1
F load() 12 105 16
B getContentConfigFile() 0 30 6
A getDefaultPathsToConfig() 0 4 1
A setDefaultPathsToConfig() 0 4 1
A addDefaultPathToConfig() 0 6 1
A getConfigFileName() 0 4 1
A setConfigFileName() 0 4 1
A getVariableResolver() 0 4 1
A removeWorkflow() 0 4 1
A saveWorkflow() 0 4 1
A getFactory() 0 4 1
A getPersistence() 0 4 1
A getPersistenceArgs() 0 4 1
A getWorkflow() 0 10 2
A getWorkflowNames() 0 6 1
B getWorkflowStore() 0 27 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
/**
3
 * @link https://github.com/old-town/old-town-workflow
4
 * @author  Malofeykin Andrey  <[email protected]>
5
 */
6
namespace OldTown\Workflow\Config;
7
8
use OldTown\Workflow\Exception\FactoryException;
9
use OldTown\Workflow\Exception\InvalidParsingWorkflowException;
10
use OldTown\Workflow\Loader\UrlWorkflowFactory;
11
use OldTown\Workflow\Loader\WorkflowDescriptor;
12
use OldTown\Workflow\Loader\WorkflowFactoryInterface;
13
use OldTown\Workflow\Loader\XmlUtil;
14
use OldTown\Workflow\Util\Properties\Properties;
15
use OldTown\Workflow\Util\VariableResolverInterface;
16
use OldTown\Workflow\Spi\WorkflowStoreInterface;
17
use OldTown\Workflow\Exception\StoreException;
18
use Psr\Http\Message\UriInterface;
19
use OldTown\Workflow\Util\DefaultVariableResolver;
20
use DOMDocument;
21
use DOMElement;
22
23
/**
24
 * Interface ConfigurationInterface
25
 *
26
 * @package OldTown\Workflow\Config
27
 */
28
class  DefaultConfiguration implements ConfigurationInterface
29
{
30
    /**
31
     * @var DefaultConfiguration
32
     */
33
    protected static $instance;
34
35
    /**
36
     * Пути по умолчнаию до файла с конфигом
37
     *
38
     * @var array
39
     */
40
    protected static $defaultPathsToConfig = [];
41
42
    /**
43
     * Имя файла конфига по умолчанию
44
     *
45
     * @var string
46
     */
47
    protected static $configFileName = 'osworkflow.xml';
48
49
    /**
50
     * Флаг определяющий было ли иницилизированно workflow
51
     *
52
     * @var bool
53
     */
54
    private $initialized = false;
55
56
    /**
57
     * @var VariableResolverInterface
58
     */
59
    private $variableResolver;
60
61
    /**
62
     * Имя класса хранилища состояния workflow
63
     *
64
     * @var string
65
     */
66
    private $persistenceClass;
67
68
    /**
69
     * Настройки хранилища
70
     *
71
     * @var array
72
     */
73
    private $persistenceArgs = [];
74
75
    /**
76
     * @var WorkflowFactoryInterface
77
     */
78
    private $factory;
79
80
    /**
81
     * Хранилище состояния workflow
82
     *
83
     * @var WorkflowStoreInterface
84
     */
85
    private $store;
86
87
    /**
88
     *
89
     */
90
    public function __construct()
91
    {
92
        $this->variableResolver = new DefaultVariableResolver();
93
        $this->factory = new UrlWorkflowFactory();
94
        $this->initDefaultPathsToConfig();
95
    }
96
97
    /**
98
     * Иницализация путей по которым происходит поиск
99
     *
100
     * @return void
101
     */
102
    protected function initDefaultPathsToConfig()
103
    {
104
        static::$defaultPathsToConfig[] = __DIR__ . '/../../config';
105
    }
106
107
    /**
108
     * @param string $workflowName
109
     * @param object $layout
110
     * @return void
111
     */
112
    public function setLayout($workflowName, $layout)
0 ignored issues
show
Unused Code introduced by
The parameter $workflowName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $layout is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
113
    {
114
    }
115
116
    /**
117
     * @param string $workflowName
118
     * @return Object
119
     */
120
    public function getLayout($workflowName)
0 ignored issues
show
Unused Code introduced by
The parameter $workflowName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
121
    {
122
        return null;
123
    }
124
125
    /**
126
     * @param string $name
127
     * @return boolean
128
     */
129
    public function isModifiable($name)
130
    {
131
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface OldTown\Workflow\Config\...Interface::isModifiable of type OldTown\Workflow\Config\true.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
132
    }
133
134
    /**
135
     *
136
     * @return string
137
     */
138
    public function getName()
139
    {
140
        return '';
141
    }
142
143
    /**
144
     * Возможность статически получать экземпляр конфигурации по умолчанию для workflow
145
     *
146
     * @return DefaultConfiguration
147
     */
148
    public static function getInstance()
149
    {
150
        if (self::$instance instanceof self) {
151
            return self::$instance;
152
        }
153
154
        self::$instance = new self();
155
156
        return self::$instance;
157
    }
158
159
    /**
160
     * Возвращает true, если фабрика инициализировала объект конфигурации
161
     *
162
     * @return boolean
163
     */
164
    public function isInitialized()
165
    {
166
        return $this->initialized;
167
    }
168
169
    /**
170
     * Загружает указанный файл конфигурации
171
     *
172
     * @param UriInterface|null $url
173
     * @return void
174
     * @throws FactoryException
175
     */
176
    public function load(UriInterface $url = null)
177
    {
178
        try {
179
            $content = $this->getContentConfigFile($url);
180
181
            libxml_use_internal_errors(true);
182
183
184
            $xmlDoc = new DOMDocument();
185
            $resultLoadXml = $xmlDoc->loadXML($content);
186
187 View Code Duplication
            if (!$resultLoadXml) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
188
                $error = libxml_get_last_error();
189
                if ($error instanceof \LibXMLError) {
190
                    $errMsg = "Error in workflow xml.\n";
191
                    $errMsg .= "Message: {$error->message}.\n";
192
                    $errMsg .= "File: {$error->file}.\n";
193
                    $errMsg .= "Line: {$error->line}.\n";
194
                    $errMsg .= "Column: {$error->column}.";
195
196
                    throw new InvalidParsingWorkflowException($errMsg);
197
                }
198
            }
199
            /** @var DOMElement $root */
200
            $root = $xmlDoc->getElementsByTagName('osworkflow')->item(0);
201
202
203
            $p = XmlUtil::getChildElement($root, 'persistence');
204
            $resolver = XmlUtil::getChildElement($root, 'resolver');
205
            $factoryElement =  XmlUtil::getChildElement($root, 'factory');
206
207
208
            if (null !== $resolver && $resolver->hasAttribute('class')) {
209
                $resolverClass = XmlUtil::getRequiredAttributeValue($resolver, 'class');
210
211
                if (!class_exists($resolverClass)) {
212
                    $errMsg = "Для variableResolver указан не существующий класс {$resolverClass}";
213
                    throw new FactoryException($errMsg);
214
                }
215
216
                $variableResolver = new $resolverClass();
217
                if (!$variableResolver instanceof VariableResolverInterface) {
218
                    $errMsg = 'variableResolver должен реализовывать интерфейс VariableResolverInterface';
219
                    throw new FactoryException($errMsg);
220
                }
221
                $this->variableResolver = $variableResolver;
222
            }
223
224
            $this->persistenceClass = XmlUtil::getRequiredAttributeValue($p, 'class');
0 ignored issues
show
Bug introduced by
It seems like $p defined by \OldTown\Workflow\Loader...t($root, 'persistence') on line 203 can be null; however, OldTown\Workflow\Loader\...equiredAttributeValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
225
226
            $args = XmlUtil::getChildElements($p, 'property');
0 ignored issues
show
Bug introduced by
It seems like $p defined by \OldTown\Workflow\Loader...t($root, 'persistence') on line 203 can be null; however, OldTown\Workflow\Loader\...til::getChildElements() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
227
228
            foreach ($args as $arg) {
229
                $key = XmlUtil::getRequiredAttributeValue($arg, 'key');
230
                $value = XmlUtil::getRequiredAttributeValue($arg, 'value');
231
                $this->persistenceArgs[$key] = $value;
232
            }
233
234
            if (null !== $factoryElement) {
235
                $class = null;
236
                try {
237
                    $factoryClassName = XmlUtil::getRequiredAttributeValue($factoryElement, 'class');
238
239
                    if (!class_exists($factoryClassName)) {
240
                        $errMsg = "Для фабрики workflow указан несуществующий класс {$factoryClassName}";
241
                        throw new FactoryException($errMsg);
242
                    }
243
                    /** @var WorkflowFactoryInterface $factory */
244
                    $factory = new $factoryClassName();
245
246
                    if (!$factory instanceof WorkflowFactoryInterface) {
247
                        $errMsg = 'Фабрика должна реализовывать интерфейся WorkflowFactoryInterface';
248
                        throw new FactoryException($errMsg);
249
                    }
250
251
                    $properties = new Properties();
252
                    $props = XmlUtil::getChildElements($factoryElement, 'property');
253
254
                    foreach ($props as $e) {
255
                        $key = XmlUtil::getRequiredAttributeValue($e, 'key');
256
                        $value = XmlUtil::getRequiredAttributeValue($e, 'value');
257
                        $properties->setProperty($key, $value);
258
                    }
259
260
                    $factory->init($properties);
261
                    $factory->initDone();
262
263
                    $this->factory = $factory;
264
                } catch (FactoryException $e) {
265
                    throw $e;
266
                } catch (\Exception $e) {
267
                    $class = (string)$class;
268
                    $errMsg = "Ошибка создания фабрики workflow для класса {$class}";
269
                    throw new FactoryException($errMsg, $e->getCode(), $e);
270
                }
271
272
                $this->initialized = true;
273
            }
274
        } catch (FactoryException $e) {
275
            throw $e;
276
        } catch (\Exception $e) {
277
            $errMsg = 'Ошибка при работе с конфигом workflow';
278
            throw new FactoryException($errMsg, $e->getCode(), $e);
279
        }
280
    }
281
282
283
    /**
284
     * @param UriInterface $url
285
     *
286
     * @return string
287
     *
288
     * @throws FactoryException
289
     */
290
    protected function getContentConfigFile(UriInterface $url = null)
291
    {
292
        if (null !== $url) {
293
            $urlStr = (string)$url;
294
            $content = file_get_contents($urlStr);
295
296
            return $content;
297
        }
298
299
        $paths = static::getDefaultPathsToConfig();
300
301
        $content = null;
302
        foreach ($paths as $path) {
303
            $path = realpath($path);
304
            if ($path) {
305
                $filePath = $path . DIRECTORY_SEPARATOR . static::getConfigFileName();
306
                if (file_exists($filePath)) {
307
                    $content = file_get_contents($filePath);
308
                    break;
309
                }
310
            }
311
        }
312
313
        if (null === $content) {
314
            $errMsg = 'Не удалось прочитать конфигурационный файл';
315
            throw new FactoryException($errMsg);
316
        }
317
318
        return $content;
319
    }
320
321
    /**
322
     * @return array
323
     */
324
    public static function getDefaultPathsToConfig()
325
    {
326
        return self::$defaultPathsToConfig;
327
    }
328
329
    /**
330
     * @param array $defaultPathsToConfig
331
     */
332
    public static function setDefaultPathsToConfig(array $defaultPathsToConfig = [])
333
    {
334
        self::$defaultPathsToConfig = $defaultPathsToConfig;
335
    }
336
337
    /**
338
     * @param string $path
339
     */
340
    public static function addDefaultPathToConfig($path)
341
    {
342
        $path = (string)$path;
343
344
        array_unshift(self::$defaultPathsToConfig, $path);
345
    }
346
347
348
    /**
349
     * @return string
350
     */
351
    public static function getConfigFileName()
352
    {
353
        return self::$configFileName;
354
    }
355
356
    /**
357
     * @param string $configFileName
358
     */
359
    public static function setConfigFileName($configFileName)
360
    {
361
        self::$configFileName = $configFileName;
362
    }
363
364
365
    /**
366
     * Возвращает resolver для работы с переменными
367
     *
368
     * @return VariableResolverInterface|DefaultVariableResolver
369
     */
370
    public function getVariableResolver()
371
    {
372
        return $this->variableResolver;
373
    }
374
375
376
    /**
377
     * Удаляет workflow
378
     *
379
     * @param string $workflow имя удаляемого workflow
380
     * @return boolean в случае успешного удаления возвращает true, в противном случае false
381
     * @throws FactoryException
382
     */
383
    public function removeWorkflow($workflow)
384
    {
385
        $this->getFactory()->removeWorkflow($workflow);
386
    }
387
388
    /**
389
     * Сохраняет Workflow
390
     * @param string $name имя сохраняемого workflow
391
     * @param WorkflowDescriptor $descriptor дескриптор workflow
392
     * @param boolean $replace - флаг определяющий, можно ли замениить workflow
393
     *
394
     * @return boolean
395
     *
396
     * @throws FactoryException
397
     * @throws \OldTown\Workflow\Exception\InvalidWorkflowDescriptorException
398
     *
399
     */
400
    public function saveWorkflow($name, WorkflowDescriptor $descriptor, $replace = false)
401
    {
402
        $this->getFactory()->saveWorkflow($name, $descriptor, $replace);
403
    }
404
405
    /**
406
     * @return WorkflowFactoryInterface
407
     */
408
    public function getFactory()
409
    {
410
        return $this->factory;
411
    }
412
413
    /**
414
     * Возвращает имя класса описвающего хранилидище, в котором сохраняется workflow
415
     *
416
     * @return string
417
     */
418
    public function getPersistence()
419
    {
420
        return $this->persistenceClass;
421
    }
422
423
    /**
424
     * Получить аргументы хранилища
425
     *
426
     * @return array
427
     */
428
    public function getPersistenceArgs()
429
    {
430
        return $this->persistenceArgs;
431
    }
432
433
434
    /**
435
     * Возвращает имя дескриптора workflow
436
     *
437
     * @param string $name имя workflow
438
     * @throws FactoryException
439
     * @return WorkflowDescriptor
440
     */
441
    public function getWorkflow($name)
442
    {
443
        $workflow = $this->getFactory()->getWorkflow($name);
444
445
        if (!$workflow instanceof WorkflowDescriptor) {
446
            throw new FactoryException('Unknown workflow name');
447
        }
448
449
        return $workflow;
450
    }
451
452
    /**
453
     * Получает список имен всех доступных workflow
454
     * @throws FactoryException
455
     * @return String[]
456
     */
457
    public function getWorkflowNames()
458
    {
459
        $names = $this->getFactory()->getWorkflowNames();
460
461
        return $names;
462
    }
463
464
    /**
465
     * Получает хранилище Workflow
466
     *
467
     * @return WorkflowStoreInterface
468
     * @throws StoreException
469
     * @throws \OldTown\Workflow\Exception\FactoryException
470
     */
471
    public function getWorkflowStore()
472
    {
473
        if (!$this->store) {
474
            $class = $this->getPersistence();
475
476
            if (!class_exists($class)) {
477
                $errMsg = sprintf(
478
                    'Отсутствует класс хранилища %s',
479
                    $class
480
                );
481
                throw new FactoryException($errMsg);
482
            }
483
484
485
            $store = new $class();
486
            if (!$store instanceof WorkflowStoreInterface) {
487
                throw new FactoryException('Ошибка при создание хранилища');
488
            }
489
490
            $storeArgs = $this->getPersistenceArgs();
491
            $store->init($storeArgs);
492
493
            $this->store = $store;
494
        }
495
496
        return $this->store;
497
    }
498
}
499