Config   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 334
Duplicated Lines 2.99 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 10
loc 334
rs 8.96
c 0
b 0
f 0
wmc 43
lcom 1
cbo 11

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A checkConfigCommandAlias() 0 30 5
A registerConfigCommandAlias() 0 17 4
A registerCustomCommands() 0 33 4
B newCommand() 0 26 6
A registerCustomAutoloaders() 10 16 3
A setConfig() 0 4 1
A getConfig() 0 8 2
A setLoader() 0 4 1
A getLoader() 0 9 2
A load() 0 4 1
A loadPartialConfig() 0 5 1
A getDetectSubFolders() 0 8 2
A createLoader() 0 8 1
A debugWriteln() 0 7 2
A getArray() 0 9 2
A traverse() 0 16 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 Config 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 Config, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * @author Tom Klingenberg <https://github.com/ktomk>
4
 */
5
6
namespace N98\Magento\Application;
7
8
use Composer\Autoload\ClassLoader;
9
use InvalidArgumentException;
10
use N98\Magento\Application;
11
use N98\Util\ArrayFunctions;
12
use N98\Util\BinaryString;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Formatter\OutputFormatter;
15
use Symfony\Component\Console\Input\ArgvInput;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Symfony\Component\Console\Output\NullOutput;
18
use Symfony\Component\Console\Output\OutputInterface;
19
20
/**
21
 * Class Config
22
 *
23
 * Class representing the application configuration. Created to factor out configuration related application
24
 * functionality from @see \N98\Magento\Application
25
 *
26
 * @package N98\Magento\Application
27
 */
28
class Config
29
{
30
    const PSR_0 = 'PSR-0';
31
    const PSR_4 = 'PSR-4';
32
33
    const COMMAND_CLASS = 'Symfony\Component\Console\Command\Command';
34
35
    /**
36
     * @var array config data
37
     */
38
    private $config = array();
39
40
    /**
41
     * @var array
42
     */
43
    private $partialConfig = array();
44
45
    /**
46
     * @var ConfigurationLoader
47
     */
48
    private $loader;
49
50
    /**
51
     * @var array
52
     */
53
    private $initConfig = array();
54
55
    /**
56
     * @var boolean
57
     */
58
    private $isPharMode;
59
60
    /**
61
     * @var OutputInterface
62
     */
63
    private $output;
64
65
    /**
66
     * Config constructor.
67
     *
68
     * @param array $initConfig
69
     * @param bool $isPharMode
70
     * @param OutputInterface $output [optional]
71
     */
72
    public function __construct(array $initConfig = array(), $isPharMode = false, OutputInterface $output = null)
73
    {
74
        $this->initConfig = $initConfig;
75
        $this->isPharMode = (bool) $isPharMode;
76
        $this->output = $output ?: new NullOutput();
77
    }
78
79
    /**
80
     * alias magerun command in input from config
81
     *
82
     * @param InputInterface $input
83
     * @return ArgvInput|InputInterface
84
     */
85
    public function checkConfigCommandAlias(InputInterface $input)
0 ignored issues
show
Coding Style introduced by
checkConfigCommandAlias uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
86
    {
87
        foreach ($this->getArray(array('commands', 'aliases')) as $alias) {
88
            if (!is_array($alias)) {
89
                continue;
90
            }
91
            $aliasCommandName = key($alias);
92
            if ($input->getFirstArgument() !== $aliasCommandName) {
93
                continue;
94
            }
95
            $aliasCommandParams = array_slice(
96
                BinaryString::trimExplodeEmpty(' ', $alias[$aliasCommandName]),
97
                1
98
            );
99
            if (0 === count($aliasCommandParams)) {
100
                continue;
101
            }
102
103
            // replace command (?) with aliased data
104
            $oldArgv = $_SERVER['argv'];
105
            $newArgv = array_merge(
106
                array_slice($oldArgv, 0, 2),
107
                $aliasCommandParams,
108
                array_slice($oldArgv, 2)
109
            );
110
            $input = new ArgvInput($newArgv);
111
        }
112
113
        return $input;
114
    }
115
116
    /**
117
     * @param Command $command
118
     */
119
    public function registerConfigCommandAlias(Command $command)
120
    {
121
        foreach ($this->getArray(array('commands', 'aliases')) as $alias) {
122
            if (!is_array($alias)) {
123
                continue;
124
            }
125
126
            $aliasCommandName = key($alias);
127
            $commandString = $alias[$aliasCommandName];
128
            list($originalCommand) = explode(' ', $commandString, 2);
129
            if ($command->getName() !== $originalCommand) {
130
                continue;
131
            }
132
133
            $command->setAliases(array_merge($command->getAliases(), array($aliasCommandName)));
134
        }
135
    }
136
137
    /**
138
     * @param Application $application
139
     */
140
    public function registerCustomCommands(Application $application)
141
    {
142
        foreach ($this->getArray(array('commands', 'customCommands')) as $commandClass) {
143
            $commandName = null;
144
            if (is_array($commandClass)) {
145
                // Support for key => value (name -> class)
146
                $commandName = key($commandClass);
147
                $commandClass = current($commandClass);
148
            }
149
            if (null === $command = $this->newCommand($commandClass, $commandName)) {
150
                $this->output->writeln(
151
                    sprintf(
152
                        '<error>Can not add nonexistent command class "%s" as command to the application</error>',
153
                        $commandClass,
154
                        $commandName
155
                    )
156
                );
157
                $this->debugWriteln(
158
                    'Please check the configuration files contain the correct class-name. If the ' .
159
                    'class-name is correct, check autoloader configurations.'
160
                );
161
            } else {
162
                $this->debugWriteln(
163
                    sprintf(
164
                        '<debug>Add command </debug> <info>%s</info> -> <comment>%s</comment>',
165
                        $command->getName(),
166
                        get_class($command)
167
                    )
168
                );
169
                $application->add($command);
170
            }
171
        }
172
    }
173
174
    /**
175
     * @param string $className
176
     * @param string|null $commandName
177
     * @return Command
178
     * @throws InvalidArgumentException
179
     */
180
    private function newCommand($className, $commandName)
181
    {
182
        /** @var Command $command */
183
        if (!(is_string($className) || is_object($className))) {
184
            throw new InvalidArgumentException(
185
                sprintf('Command classname must be string, %s given', gettype($className))
186
            );
187
        }
188
189
        if (!class_exists($className)) {
190
            return null;
191
        }
192
193
        if (false === is_subclass_of($className, self::COMMAND_CLASS, true)) {
194
            throw new InvalidArgumentException(
195
                sprintf('Class "%s" is not a Command (subclass of "%s")', $className, self::COMMAND_CLASS)
196
            );
197
        }
198
199
        $command = new $className();
200
        if (null !== $commandName) {
201
            $command->setName($commandName);
202
        }
203
204
        return $command;
205
    }
206
207
    /**
208
     * Adds autoloader prefixes from user's config
209
     *
210
     * @param ClassLoader $autoloader
211
     */
212
    public function registerCustomAutoloaders(ClassLoader $autoloader)
213
    {
214
        $mask = '<debug>Registered %s autoloader </debug> <info>%s</info> -> <comment>%s</comment>';
215
216 View Code Duplication
        foreach ($this->getArray('autoloaders') as $prefix => $paths) {
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...
217
            $paths = (array) $paths;
218
            $this->debugWriteln(sprintf($mask, self::PSR_0, OutputFormatter::escape($prefix), implode(",", $paths)));
219
            $autoloader->add($prefix, $paths);
220
        }
221
222 View Code Duplication
        foreach ($this->getArray('autoloaders_psr4') as $prefix => $paths) {
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...
223
            $paths = (array) $paths;
224
            $this->debugWriteln(sprintf($mask, self::PSR_4, OutputFormatter::escape($prefix), implode(",", $paths)));
225
            $autoloader->addPsr4($prefix, $paths);
226
        }
227
    }
228
229
    /**
230
     * @param array $config
231
     */
232
    public function setConfig(array $config)
233
    {
234
        $this->config = $config;
235
    }
236
237
    /**
238
     * Get config array (whole or in part)
239
     *
240
     * @param string|array $key
241
     * @return array
242
     */
243
    public function getConfig($key = null)
244
    {
245
        if (null === $key) {
246
            return $this->config;
247
        }
248
249
        return $this->getArray($key);
250
    }
251
252
    /**
253
     * @param ConfigurationLoader $configurationLoader
254
     */
255
    public function setLoader(ConfigurationLoader $configurationLoader)
256
    {
257
        $this->loader = $configurationLoader;
258
    }
259
260
    /**
261
     * @return ConfigurationLoader
262
     */
263
    public function getLoader()
264
    {
265
        if (!$this->loader) {
266
            $this->loader = $this->createLoader($this->initConfig, $this->isPharMode, $this->output);
267
            $this->initConfig = array();
268
        }
269
270
        return $this->loader;
271
    }
272
273
    public function load()
274
    {
275
        $this->config = $this->getLoader()->toArray();
276
    }
277
278
    /**
279
     * @param bool $loadExternalConfig
280
     */
281
    public function loadPartialConfig($loadExternalConfig)
282
    {
283
        $loader = $this->getLoader();
284
        $this->partialConfig = $loader->getPartialConfig($loadExternalConfig);
285
    }
286
287
    /**
288
     * Get names of sub-folders to be scanned during Magento detection
289
     *
290
     * @return array
291
     */
292
    public function getDetectSubFolders()
293
    {
294
        if (isset($this->partialConfig['detect']['subFolders'])) {
295
            return $this->partialConfig['detect']['subFolders'];
296
        }
297
298
        return array();
299
    }
300
301
    /**
302
     * @param array $initConfig
303
     * @param bool $isPharMode
304
     * @param OutputInterface $output
305
     *
306
     * @return ConfigurationLoader
307
     */
308
    public function createLoader(array $initConfig, $isPharMode, OutputInterface $output)
309
    {
310
        $config = ArrayFunctions::mergeArrays($this->config, $initConfig);
311
312
        $loader = new ConfigurationLoader($config, $isPharMode, $output);
313
314
        return $loader;
315
    }
316
317
    /**
318
     * @param string $message
319
     */
320
    private function debugWriteln($message)
321
    {
322
        $output = $this->output;
323
        if (OutputInterface::VERBOSITY_DEBUG <= $output->getVerbosity()) {
324
            $output->writeln($message);
325
        }
326
    }
327
328
    /**
329
     * Get array from config, default to an empty array if not set
330
     *
331
     * @param string|array $key
332
     * @param array $default [optional]
333
     * @return array
334
     */
335
    private function getArray($key, $default = array())
336
    {
337
        $result = $this->traverse((array) $key);
338
        if (null === $result) {
339
            return $default;
340
        }
341
342
        return $result;
343
    }
344
345
    private function traverse(array $keys)
346
    {
347
        $anchor = &$this->config;
348
        foreach ($keys as $key) {
349
            if (!is_array($anchor)) {
350
                return null;
351
            }
352
353
            if (!isset($anchor[$key])) {
354
                return null;
355
            }
356
            $anchor = &$anchor[$key];
357
        }
358
359
        return $anchor;
360
    }
361
}
362