Completed
Pull Request — master (#56)
by Robin
07:16
created

src/ConfigBridge.php (8 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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
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
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
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
100
     * @param string|array $finderDirs       A directory path or an array of directories for Finder
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());
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());
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
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
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
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
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()
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)) {
265
                array_push($fixers, $name);
266
            }
267 View Code Duplication
            if (in_array($name, $fixers, true) && !in_array($alias, $fixers, true) && $this->isFixerAvailable($alias)) {
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