Completed
Push — master ( e54d7a...9456c1 )
by Sullivan
02:56
created

ConfigBridge::create()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
c 5
b 1
f 0
dl 0
loc 26
rs 8.5806
cc 4
eloc 13
nc 8
nop 2
1
<?php
2
3
namespace SLLH\StyleCIBridge;
4
5
use Composer\Semver\Semver;
6
use SLLH\StyleCIBridge\Exception\LevelConfigException;
7
use SLLH\StyleCIBridge\StyleCI\Configuration;
8
use SLLH\StyleCIFixers\Fixers;
9
use Symfony\Component\Config\Definition\Processor;
10
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
11
use Symfony\Component\Console\Output\ConsoleOutput;
12
use Symfony\Component\Console\Output\OutputInterface;
13
use Symfony\Component\DependencyInjection\Container;
14
use Symfony\Component\Finder\Finder;
15
use Symfony\Component\Yaml\Yaml;
16
use Symfony\CS\Config\Config;
17
use Symfony\CS\Finder\DefaultFinder;
18
use Symfony\CS\Fixer;
19
use Symfony\CS\Fixer\Contrib\HeaderCommentFixer;
20
use Symfony\CS\FixerFactory;
21
use Symfony\CS\FixerInterface;
22
23
/**
24
 * @author Sullivan Senechal <[email protected]>
25
 */
26
final class ConfigBridge
27
{
28
    const CS_FIXER_MIN_VERSION = '1.6.1';
29
30
    /**
31
     * @var OutputInterface
32
     */
33
    private $output;
34
35
    /**
36
     * @var FixerFactory
37
     */
38
    private $fixerFactory = null;
39
40
    /**
41
     * @var string
42
     */
43
    private $styleCIConfigDir;
44
45
    /**
46
     * @var array|null
47
     */
48
    private $styleCIConfig = null;
49
50
    /**
51
     * @var string|array
52
     */
53
    private $finderDirs;
54
55
    /**
56
     * @param string|null       $styleCIConfigDir StyleCI config directory. Called script dir as default.
57
     * @param string|array|null $finderDirs       A directory path or an array of directories for Finder. Called script dir as default.
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 135 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
58
     */
59
    public function __construct($styleCIConfigDir = null, $finderDirs = null)
60
    {
61
        if (!Semver::satisfies(Fixer::VERSION, sprintf('>=%s', self::CS_FIXER_MIN_VERSION))) {
62
            throw new \RuntimeException(sprintf(
63
                'PHP-CS-Fixer v%s is not supported, please upgrade to v%s or higher.',
64
                Fixer::VERSION,
65
                self::CS_FIXER_MIN_VERSION
66
            ));
67
        }
68
69
        // Guess config files path if not specified.
70
        // getcwd function is not enough. See: https://github.com/Soullivaneuh/php-cs-fixer-styleci-bridge/issues/46
71
        if (null === $styleCIConfigDir || null === $finderDirs) {
72
            $dbt = version_compare(PHP_VERSION, '5.4.0', '>=') ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2) : debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 160 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
73
74
            // Static call
75
            if (isset($dbt[1]['class']) && 'SLLH\StyleCIBridge\ConfigBridge' === $dbt[1]['class'] && 'create' === $dbt[1]['function']) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
76
                $configsPath = dirname($dbt[1]['file']);
77
            } elseif (isset($dbt[0]['class']) && 'SLLH\StyleCIBridge\ConfigBridge' === $dbt[0]['class'] && '__construct' === $dbt[0]['function']) { // Manual instance
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 166 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
78
                $configsPath = dirname($dbt[0]['file']);
79
            } else { // If no case found, fallback to not reliable getcwd method.
80
                $configsPath = getcwd();
81
            }
82
83
            $this->styleCIConfigDir = $styleCIConfigDir ?: $configsPath;
84
            $this->finderDirs = $finderDirs ?: $configsPath;
85
        }
86
87
        $this->output = new ConsoleOutput();
88
        $this->output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
89
        // PHP-CS-Fixer 1.x BC
90
        if (class_exists('Symfony\CS\FixerFactory')) {
91
            $this->fixerFactory = FixerFactory::create();
92
            $this->fixerFactory->registerBuiltInFixers();
93
        }
94
95
        $this->parseStyleCIConfig();
96
    }
97
98
    /**
99
     * @param string       $styleCIConfigDir
0 ignored issues
show
Documentation introduced by
Should the type for parameter $styleCIConfigDir not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
100
     * @param string|array $finderDirs       A directory path or an array of directories for Finder
0 ignored issues
show
Documentation introduced by
Should the type for parameter $finderDirs not be string|array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
101
     *
102
     * @return Config
103
     */
104
    public static function create($styleCIConfigDir = null, $finderDirs = null)
105
    {
106
        $bridge = new static($styleCIConfigDir, $finderDirs);
107
108
        $config = Config::create();
109
110
        // PHP-CS-Fixer 1.x BC
111
        if (method_exists($config, 'level')) {
112
            $config->level(FixerInterface::NONE_LEVEL);
113
        }
114
115
        if (method_exists($config, 'setRules')) {
116
            $config->setRules($bridge->getRules());
0 ignored issues
show
Bug introduced by
The method setRules() does not seem to exist on object<Symfony\CS\Config\Config>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
117
        } else { // PHP-CS-Fixer 1.x BC
118
            $config->fixers($bridge->getFixers());
119
        }
120
121
        // PHP-CS-Fixer 1.x BC
122
        if (method_exists($config, 'setRiskyAllowed')) {
123
            $config->setRiskyAllowed($bridge->getRisky());
0 ignored issues
show
Bug introduced by
The method setRiskyAllowed() does not seem to exist on object<Symfony\CS\Config\Config>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
124
        }
125
126
        return $config
127
            ->finder($bridge->getFinder())
128
        ;
129
    }
130
131
    /**
132
     * @return Finder
133
     */
134
    public function getFinder()
135
    {
136
        $finder = DefaultFinder::create()->in($this->finderDirs);
137
        if (isset($this->styleCIConfig['finder'])) {
138
            $finderConfig = $this->styleCIConfig['finder'];
139
            foreach ($finderConfig as $key => $values) {
140
                $finderMethod = Container::camelize($key);
141
                foreach ($values as $value) {
142
                    if (method_exists($finder, $finderMethod)) {
143
                        $finder->$finderMethod($value);
144
                    } else {
145
                        $this->output->writeln(sprintf(
146
                            '<warning>Can not apply "%s" finder option with PHP-CS-Fixer v%s. You fixer config may be erroneous. Consider upgrading to fix it.</warning>',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 170 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
147
                            str_replace('_', '-', $key),
148
                            Fixer::VERSION
149
                        ));
150
                    }
151
                }
152
            }
153
        }
154
155
        return $finder;
156
    }
157
158
    /**
159
     * @return int
160
     *
161
     * @deprecated since 1.1, to be removed in 2.0
162
     */
163
    public function getLevel()
164
    {
165
        @trigger_error('The '.__METHOD__.' method is deprecated since version 1.1 and will be removed in 2.0.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 131 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
166
167
        $preset = $this->styleCIConfig['preset'];
168
        $validPresets = array(
169
            'psr1'    => FixerInterface::PSR1_LEVEL,
170
            'psr2'    => FixerInterface::PSR1_LEVEL,
171
            'symfony' => FixerInterface::SYMFONY_LEVEL,
172
        );
173
        if (!in_array($preset, array_keys($validPresets))) {
174
            throw new LevelConfigException(sprintf('Invalid preset "%s". Must be one of "%s".', $preset, implode('", "', array_keys($validPresets))));
0 ignored issues
show
Deprecated Code introduced by
The class SLLH\StyleCIBridge\Exception\LevelConfigException has been deprecated with message: since 1.1, will be removed in 2.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 150 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
175
        }
176
177
        return $validPresets[$preset];
178
    }
179
180
    /**
181
     * @return string[]
182
     */
183
    public function getFixers()
184
    {
185
        $presetFixers = $this->resolveAliases($this->getPresetFixers());
186
        $enabledFixers = $this->resolveAliases($this->styleCIConfig['enabled']);
187
        $disabledFixers = $this->resolveAliases($this->styleCIConfig['disabled']);
188
189
        $fixers = array_merge(
190
            $enabledFixers,
191
            array_map(function ($disabledFixer) {
192
                return '-'.$disabledFixer;
193
            }, $disabledFixers),
194
            array_diff($presetFixers, $disabledFixers) // Remove disabled fixers from preset
195
        );
196
197
        // PHP-CS-Fixer 1.x BC
198
        if (method_exists('Symfony\CS\Fixer\Contrib\HeaderCommentFixer', 'getHeader') && HeaderCommentFixer::getHeader()) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 123 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
199
            array_push($fixers, 'header_comment');
200
        }
201
202
        return $fixers;
203
    }
204
205
    /**
206
     * Returns fixers converted to rules for PHP-CS-Fixer 2.x.
207
     *
208
     * @return array
209
     */
210
    public function getRules()
211
    {
212
        $fixers = $this->getFixers();
213
214
        $rules = array();
215
        foreach ($fixers as $fixer) {
216
            if ('-' === $fixer[0]) {
217
                $name = substr($fixer, 1);
218
                $enabled = false;
219
            } else {
220
                $name = $fixer;
221
                $enabled = true;
222
            }
223
224
            if ($this->isFixerAvailable($name)) {
225
                $rules[$name] = $enabled;
226
            } else {
227
                $this->output->writeln(sprintf('<warning>Fixer "%s" does not exist, skipping.</warning>', $name));
228
            }
229
        }
230
231
        return $rules;
232
    }
233
234
    /**
235
     * @return bool
236
     */
237
    public function getRisky()
0 ignored issues
show
Coding Style introduced by
function getRisky() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
238
    {
239
        return $this->styleCIConfig['risky'];
240
    }
241
242
    /**
243
     * @return string[]
244
     */
245
    private function getPresetFixers()
246
    {
247
        $preset = $this->styleCIConfig['preset'];
248
        $validPresets = Fixers::getPresets();
249
250
        return $validPresets[$preset];
251
    }
252
253
    /**
254
     * Adds both aliases and real fixers if set. PHP-CS-Fixer would not take care if not existing.
255
     * Better compatibility between PHP-CS-Fixer 1.x and 2.x.
256
     *
257
     * @param string[] $fixers
258
     *
259
     * @return string[]
260
     */
261
    private function resolveAliases(array $fixers)
262
    {
263
        foreach (Fixers::$aliases as $alias => $name) {
264 View Code Duplication
            if (in_array($alias, $fixers, true) && !in_array($name, $fixers, true) && $this->isFixerAvailable($name)) {
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...
265
                array_push($fixers, $name);
266
            }
267 View Code Duplication
            if (in_array($name, $fixers, true) && !in_array($alias, $fixers, true) && $this->isFixerAvailable($alias)) {
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...
268
                array_push($fixers, $alias);
269
            }
270
        }
271
272
        return $fixers;
273
    }
274
275
    /**
276
     * @param string $name
277
     *
278
     * @return bool
279
     */
280
    private function isFixerAvailable($name)
281
    {
282
        // PHP-CS-Fixer 1.x BC
283
        if (null === $this->fixerFactory) {
284
            return true;
285
        }
286
287
        return $this->fixerFactory->hasRule($name);
288
    }
289
290
    private function parseStyleCIConfig()
291
    {
292
        if (null === $this->styleCIConfig) {
293
            $config = Yaml::parse(file_get_contents(sprintf('%s/.styleci.yml', $this->styleCIConfigDir)));
294
            $processor = new Processor();
295
            $this->styleCIConfig = $processor->processConfiguration(new Configuration(), array('styleci' => $config));
296
        }
297
    }
298
}
299