Completed
Push — master ( 14e077...a99888 )
by Ivannis Suárez
05:02
created

CodeQualityTool::getConfig()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 2
eloc 2
nc 2
nop 2
1
<?php
2
3
/**
4
 * This file is part of the Cubiche package.
5
 *
6
 * Copyright (c) Cubiche
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
namespace Cubiche\Tools;
12
13
use Composer\Script\Event;
14
use Symfony\Component\Console\Application;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use Symfony\Component\Process\ProcessBuilder;
18
use Symfony\Component\Yaml\Yaml;
19
20
/**
21
 * CodeQualityTool.
22
 *
23
 * @author Karel Osorio Ramírez <[email protected]>
24
 * @author Ivannis Suárez Jérez <[email protected]>
25
 */
26
class CodeQualityTool extends Application
27
{
28
    const CONFIG_FILE = 'quality.yml';
29
30
    /**
31
     * @var OutputInterface
32
     */
33
    protected $output;
34
35
    /**
36
     * @var InputInterface
37
     */
38
    protected $input;
39
40
    /**
41
     * @var array
42
     */
43
    protected $config;
44
45
    /**
46
     * CodeQualityTool constructor.
47
     */
48
    public function __construct()
49
    {
50
        parent::__construct('Cubiche Code Quality Tool', '1.0.0');
51
52
        $this->config = file_exists(self::CONFIG_FILE) ? Yaml::parse(file_get_contents(self::CONFIG_FILE)) : array();
53
    }
54
55
    /**
56
     * {@inheritdoc}
57
     *
58
     * @see \Symfony\Component\Console\Application::doRun()
59
     */
60
    public function doRun(InputInterface $input, OutputInterface $output)
61
    {
62
        $this->input = $input;
63
        $this->output = $output;
64
65
        $output->writeln(
66
            \sprintf(
67
                '<fg=white;options=bold;bg=blue>%s %s</fg=white;options=bold;bg=blue>',
68
                $this->getName(),
69
                $this->getVersion()
70
            )
71
        );
72
73
        $output->writeln('<info>Check composer</info>');
74
        $this->checkComposer();
75
76
        $output->writeln('<info>Running PHPLint</info>');
77
        if (!$this->phpLint()) {
78
            throw new \Exception('There are some PHP syntax errors!');
79
        }
80
81
        $output->writeln('<info>Checking code style</info>');
82
        if (!$this->codeStyle()) {
83
            throw new \Exception(sprintf('There are coding standards violations!'));
84
        }
85
86
        $output->writeln('<info>Checking code style with PHPCS</info>');
87
        if (!$this->codeStylePsr()) {
88
            throw new \Exception(sprintf('There are PHPCS coding standards violations!'));
89
        }
90
91
        $output->writeln('<info>Checking code mess with PHPMD</info>');
92
        if (!$this->phPmd()) {
93
            throw new \Exception(sprintf('There are PHPMD violations!'));
94
        }
95
96
        $output->writeln('<info>Running unit tests</info>');
97
        if (!$this->unitTests()) {
98
            throw new \Exception('Fix the unit tests!');
99
        }
100
101
        $output->writeln('<info>Good job dude!</info>');
102
    }
103
104
    private function checkComposer()
105
    {
106
        $composerJsonDetected = false;
107
        $composerLockDetected = false;
108
        foreach (GitUtils::extractCommitedFiles() as $file) {
0 ignored issues
show
Bug introduced by
The expression \Cubiche\Tools\GitUtils::extractCommitedFiles() of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
109
            if ($file === 'composer.json') {
110
                $composerJsonDetected = true;
111
            }
112
            if ($file === 'composer.lock') {
113
                $composerLockDetected = true;
114
            }
115
        }
116
        if ($composerJsonDetected && !$composerLockDetected) {
117
            $this->output->writeln(
118
                '<bg=yellow;fg=black>
119
                 composer.lock must be commited if composer.json is modified!
120
                 </bg=yellow;fg=black>'
121
            );
122
        }
123
    }
124
125
    /**
126
     * @return bool
127
     */
128
    protected function phpLint()
129
    {
130
        $needle = '/(\.php)|(\.inc)$/';
131
        $succeed = true;
132
        $config = $this->getConfig('phplint', array(
133
            'triggered_by' => 'php'
134
        ));
135
136
        foreach (GitUtils::commitedFiles($needle) as $file) {
137
            $processBuilder = new ProcessBuilder(array($config['triggered_by'], '-l', $file));
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Process\ProcessBuilder has been deprecated with message: since version 3.4, to be removed in 4.0. Use the Process class instead.

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...
138
            $process = $processBuilder->getProcess();
139
            $process->run();
140
            if (!$process->isSuccessful()) {
141
                $this->output->writeln($file);
142
                $this->output->writeln(sprintf('<error>%s</error>', trim($process->getErrorOutput())));
143
                $succeed = false;
144
            }
145
        }
146
147
        return $succeed;
148
    }
149
150
    /**
151
     * @return bool
152
     */
153
    protected function phPmd()
154
    {
155
        $succeed = true;
156
        $config = $this->getConfig('phpmd', array(
157
            'ruleset' => 'controversial',
158
            'triggered_by' => 'php'
159
        ));
160
161
        $rootPath = realpath(getcwd());
162
        foreach (GitUtils::commitedFiles() as $file) {
163
            $processBuilder = new ProcessBuilder(
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Process\ProcessBuilder has been deprecated with message: since version 3.4, to be removed in 4.0. Use the Process class instead.

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...
164
                array($config['triggered_by'], 'bin/phpmd', $file, 'text', implode(',', $config['ruleset']))
165
            );
166
            $processBuilder->setWorkingDirectory($rootPath);
167
            $process = $processBuilder->getProcess();
168
            $process->run();
169
            if (!$process->isSuccessful()) {
170
                $this->output->writeln($file);
171
                $this->output->writeln(sprintf('<error>%s</error>', trim($process->getErrorOutput())));
172
                $this->output->writeln(sprintf('<info>%s</info>', trim($process->getOutput())));
173
                $succeed = false;
174
            }
175
        }
176
177
        return $succeed;
178
    }
179
180
    /**
181
     * @return bool
182
     */
183
    private function unitTests()
184
    {
185
        $succeed = true;
186
        $config = $this->getConfig('test', array(
187
            'suites' => array()
188
        ));
189
190
        if (count($config['suites']) > 0) {
191
            foreach ($config['suites'] as $suite) {
192
                $trigger = isset($suite['triggered_by']) ? $suite['triggered_by'] : 'php';
193
194
                $configFile = '.atoum.php';
195
                if (isset($suite['config_file']) && $suite['config_file'] !== null) {
196
                    $configFile = $suite['config_file'];
197
                }
198
199
                $bootstrapFile = '.bootstrap.atoum.php';
200
                if (isset($suite['bootstrap_file']) && $suite['bootstrap_file'] !== null) {
201
                    $bootstrapFile = $suite['bootstrap_file'];
202
                }
203
204
                $arguments = array(
205
                    $trigger,
206
                    'bin/atoum',
207
                    '-c',
208
                    $configFile,
209
                    '-bf',
210
                    $bootstrapFile,
211
                );
212
213
                if (isset($suite['directories']) && $suite['directories'] !== null) {
214
                    $arguments[] = '-d';
215
                    $arguments[] = implode(" ", $suite['directories']);
216
                }
217
218
                $processBuilder = new ProcessBuilder($arguments);
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Process\ProcessBuilder has been deprecated with message: since version 3.4, to be removed in 4.0. Use the Process class instead.

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...
219
                $processBuilder->setWorkingDirectory(getcwd());
220
                $processBuilder->setTimeout(null);
221
222
                $test = $processBuilder->getProcess();
223
                $test->run(
224
                    function ($type, $buffer) {
225
                        $this->output->write($buffer);
226
                    }
227
                );
228
229
                $succeed = $succeed && $test->isSuccessful();
230
            }
231
232
            return $succeed;
233
        } else {
234
            $this->output->writeln('<comment>There is no tests configuration suites</comment>');
235
236
            return true;
237
        }
238
    }
239
240
    /**
241
     * @return bool
242
     */
243
    protected function codeStyle()
244
    {
245
        $succeed = true;
246
        $config = $this->getConfig('phpcsfixer', array(
247
            'fixers' => [
248
                '-psr0','eof_ending','indentation','linefeed','lowercase_keywords','trailing_spaces',
249
                'short_tag','php_closing_tag','extra_empty_lines','elseif','function_declaration'
250
            ],
251
            'triggered_by' => 'php'
252
        ));
253
254
        foreach (GitUtils::commitedFiles() as $file) {
255
            $fixers = implode(',', $config);
256
            $processBuilder = new ProcessBuilder(array(
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Process\ProcessBuilder has been deprecated with message: since version 3.4, to be removed in 4.0. Use the Process class instead.

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...
257
                $config['triggered_by'],
258
                'bin/php-cs-fixer',
259
                '--dry-run',
260
                '--verbose',
261
                'fix',
262
                $file,
263
                '--fixers='.$fixers,
264
            ));
265
266
            $processBuilder->setWorkingDirectory(getcwd());
267
            $phpCsFixer = $processBuilder->getProcess();
268
            $phpCsFixer->run();
269
            if (!$phpCsFixer->isSuccessful()) {
270
                $this->output->writeln(sprintf('<error>%s</error>', trim($phpCsFixer->getOutput())));
271
                $succeed = false;
272
            }
273
        }
274
275
        return $succeed;
276
    }
277
278
    /**
279
     * @return bool
280
     */
281
    private function codeStylePsr()
282
    {
283
        $succeed = true;
284
        $config = $this->getConfig('phpcs', array(
285
            'standard' => 'PSR2',
286
            'triggered_by' => 'php'
287
        ));
288
289
        foreach (GitUtils::commitedFiles() as $file) {
290
            $processBuilder = new ProcessBuilder(
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Process\ProcessBuilder has been deprecated with message: since version 3.4, to be removed in 4.0. Use the Process class instead.

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...
291
                array($config['triggered_by'], 'bin/phpcs', '--standard='.$config['standard'], $file)
292
            );
293
            $processBuilder->setWorkingDirectory(getcwd());
294
            $phpCsFixer = $processBuilder->getProcess();
295
            $phpCsFixer->run(function ($type, $buffer) {
296
                $this->output->write($buffer);
297
            });
298
299
            if (!$phpCsFixer->isSuccessful()) {
300
                $this->output->writeln(sprintf('<error>%s</error>', trim($phpCsFixer->getOutput())));
301
                $succeed = false;
302
            }
303
        }
304
305
        return $succeed;
306
    }
307
308
    /**
309
     * @param Event $event
310
     */
311
    public static function checkHooks(Event $event)
312
    {
313
        $io = $event->getIO();
314
315
        if (!is_dir(getcwd().'/.git/hooks')) {
316
            $io->write('<error>The .git/hooks directory does not exist, execute git init</error>');
317
318
            return;
319
        }
320
321
        $gitPath = getcwd().'/.git/hooks/pre-commit';
322
        $docPath = getcwd().'/bin/pre-commit';
323
        $gitHook = @file_get_contents($gitPath);
324
        $docHook = @file_get_contents($docPath);
325
        if ($gitHook !== $docHook) {
326
            self::createSymlink($gitPath, $docPath);
327
        }
328
    }
329
330
    /**
331
     * @param string $symlinkTarget
332
     * @param string $symlinkName
333
     *
334
     * @throws \Exception
335
     */
336
    private static function createSymlink($symlinkTarget, $symlinkName)
337
    {
338
        $processBuilder = new ProcessBuilder(array('rm', '-rf', $symlinkTarget));
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Process\ProcessBuilder has been deprecated with message: since version 3.4, to be removed in 4.0. Use the Process class instead.

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...
339
        $process = $processBuilder->getProcess();
340
        $process->run();
341
342
        if (symlink($symlinkName, $symlinkTarget) === false) {
343
            throw new \Exception('Error occured when trying to create a symlink.');
344
        }
345
    }
346
347
    /**
348
     * @param string $task
349
     * @param array  $defaults
350
     *
351
     * @return array|mixed
352
     */
353
    private function getConfig($task, array $defaults = array())
354
    {
355
        return isset($this->config[$task]) ? $this->config[$task] : $defaults;
356
    }
357
}
358