Verificação automática de código em 5 minutos

Este tutorial mostra como automatizar a verificação de estilo de código em seu projeto php.



Vamos ver como ficará a configuração em um novo projeto.



Passo 1 - Inicializamos o compositor (quem já o configurou, pula-o)



Para fazer isso, execute o comando na raiz do seu projeto. Se você não tem o composer instalado, você pode consultar a documentação oficial do getcomposer.org



composer init

      
      





Etapa 2 - Adicionar .gitignore



###> phpstorm ###
.idea
###< phpstorm ###

/vendor/

###> friendsofphp/php-cs-fixer ###
/.php_cs
/.php_cs.cache
###< friendsofphp/php-cs-fixer ###

      
      





Etapa 3 - Adicionar as bibliotecas necessárias



composer require --dev friendsofphp/php-cs-fixer symfony/process symfony/console  squizlabs/php_codesniffer

      
      





Etapa 4 - Adicionar um manipulador de gancho



O manipulador em si pode ser escrito em qualquer coisa, mas como o artigo é sobre php, iremos escrever código nele.



Crie um arquivo na pasta hooks / pre-commit.php
#!/usr/bin/php

<?php
define('VENDOR_DIR', __DIR__.'/../../vendor');
require VENDOR_DIR.'/autoload.php';

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Process\Process;

class CodeQualityTool extends Application
{
    /**
     * @var OutputInterface
     */
    private $output;
    /**
     * @var InputInterface
     */
    private $input;

    const PHP_FILES_IN_SRC = '/^src\/(.*)(\.php)$/';

    public function __construct()
    {
        parent::__construct('Ecombo Quality Tool', '1.0.0');
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     *
     * @return void
     * @throws \Exception
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        $this->input = $input;
        $this->output = $output;
        $output->writeln('<fg=white;options=bold;bg=red>Code Quality Tool</fg=white;options=bold;bg=red>');
        $output->writeln('<info>Fetching files</info>');
        $files = $this->extractCommitedFiles();
        $output->writeln('<info>Running PHPLint</info>');

        if (! $this->phpLint($files)) {
            throw new \Exception('There are some PHP syntax errors!');
        }

        $output->writeln('<info>Checking code style with PHPCS</info>');

        if (! $this->codeStylePsr($files)) {
            throw new \Exception(sprintf('There are PHPCS coding standards violations!'));
        }

        $output->writeln('<info>Well done!</info>');
    }

    /**
     * @return array
     */
    private function extractCommitedFiles()
    {
        $output = array();
        $against = 'HEAD';
        exec("git diff-index --cached --name-status $against | egrep '^(A|M)' | awk '{print $2;}'", $output);

        return $output;
    }

    /**
     * @param array $files
     *
     * @return bool
     *
     * @throws \Exception
     */
    private function phpLint($files)
    {
        $needle = '/(\.php)|(\.inc)$/';
        $succeed = true;

        foreach ($files as $file) {
            if (! preg_match($needle, $file)) {
                continue;
            }

            $process = new Process(['php', '-l', $file]);
            $process->run();

            if (! $process->isSuccessful()) {
                $this->output->writeln($file);
                $this->output->writeln(sprintf('<error>%s</error>', trim($process->getErrorOutput())));

                if ($succeed) {
                    $succeed = false;
                }
            }
        }

        return $succeed;
    }

    /**
     * @param array $files
     *
     * @return bool
     */
    private function codeStylePsr(array $files)
    {
        $succeed = true;
        $needle = self::PHP_FILES_IN_SRC;
        $standard = 'PSR2';

        foreach ($files as $file) {
            if (! preg_match($needle, $file)) {
                continue;
            }

            $phpCsFixer = new Process([
                'php',
                VENDOR_DIR.'/bin/phpcs',
                '-n',
                '--standard='.$standard,
                $file,
            ]);

            $phpCsFixer->setWorkingDirectory(__DIR__.'/../../');
            $phpCsFixer->run();

            if (! $phpCsFixer->isSuccessful()) {
                $this->output->writeln(sprintf('<error>%s</error>', trim($phpCsFixer->getOutput())));

                if ($succeed) {
                    $succeed = false;
                }
            }
        }

        return $succeed;
    }
}

$console = new CodeQualityTool();
$console->run();

      
      









Neste exemplo, o código passará por 3 verificações:

- verificar se há erros de sintaxe

- verificar se há PSR2 por meio do sniffer de código O



PSR2 pode ser substituído por qualquer outro que suporte o sniffer de código. A lista de padrões suportados pode ser vista digitando o comando



 vendor/bin/phpcs -i

      
      







Etapa 5 - Configurando o composer para implementar a verificação de pré-confirmação de inicialização automática



Para que o código de verificação seja executado no gancho de pré-confirmação, precisamos colocar o arquivo com o código que criamos na etapa 3 e colocá-lo na pasta .git / hooks / pre-commit. Isso pode ser feito manualmente, mas é muito mais conveniente automatizar esse assunto. Para fazer isso, precisamos escrever um manipulador que irá copiar este arquivo e pendurá-lo no evento que é chamado após a instalação do composer. Para fazer isso, faça o seguinte.

5.1 Crie o próprio manipulador que irá copiar o arquivo pre-commit.php para a pasta git hooks



Crie um arquivo src / Composer / ScriptHandler.php
<?php

namespace App\Composer;

use Composer\Script\Event;

class ScriptHandler
{
    /**
     * @param Event $event
     *
     * @return bool
     */
    public static function preHooks(Event $event)
    {
        $io = $event->getIO();
        $gitHook = '.git/hooks/pre-commit';

        if (file_exists($gitHook)) {
            unlink($gitHook);
            $io->write('<info>Pre-commit hook removed!</info>');
        }

        return true;
    }

    /**
     * @param Event $event
     *
     * @return bool
     *
     * @throws \Exception
     */
    public static function postHooks(Event $event)
    {
        /** @var array $extras */
        $extras = $event->getComposer()->getPackage()->getExtra();

        if (! array_key_exists('hooks', $extras)) {
            throw new \InvalidArgumentException('The parameter handler needs to be configured through the extra.hooks setting.');
        }
        $configs = $extras['hooks'];
        if (! array_key_exists('pre-commit', $configs)) {
            throw new \InvalidArgumentException('The parameter handler needs to be configured through the extra.hooks.pre-commit setting.');
        }

        if (file_exists('.git/hooks')) {
            /** @var \Composer\IO\IOInterface $io */
            $io = $event->getIO();
            $gitHook = '.git/hooks/pre-commit';
            $docHook = $configs['pre-commit'];
            copy($docHook, $gitHook);
            chmod($gitHook, 0777);
            $io->write('<info>Pre-commit hook created!</info>');
        }

        return true;
    }
}

      
      





5.2 composer

composer.json



    "scripts": {
        "post-install-cmd": [
            "App\\Composer\\ScriptHandler::postHooks"
        ],
        "post-update-cmd": [
            "App\\Composer\\ScriptHandler::postHooks"
        ],
        "pre-update-cmd": "App\\Composer\\ScriptHandler::preHooks",
        "pre-install-cmd": "App\\Composer\\ScriptHandler::preHooks"
    },
    "extra": {
        "hooks": {
            "pre-commit": "hooks/pre-commit.php"
        }
    }

      
      









pre-update-cmd, pre-install-cmd



- antes da instalação e atualização, o manipulador antigo



post-install-cmd, post-update-cmd



é removido - após a instalação e atualização, o manipulador será configurado para pré-confirmar



Como resultado, o filekik composer.json assumirá a seguinte forma



composer.json
{
    "name": "admin/test",
    "authors": [
        {
            "name": "vitaly.gorbunov",
            "email": "cezar62882@gmail.com"
        }
    ],
    "minimum-stability": "stable",
    "require": {},
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "scripts": {
        "post-install-cmd": [
            "App\\Composer\\ScriptHandler::postHooks"
        ],
        "post-update-cmd": [
            "App\\Composer\\ScriptHandler::postHooks"
        ],
        "pre-update-cmd": "App\\Composer\\ScriptHandler::preHooks",
        "pre-install-cmd": "App\\Composer\\ScriptHandler::preHooks"
    },
    "require-dev": {
        "friendsofphp/php-cs-fixer": "^2.16",
        "symfony/process": "^5.0",
        "symfony/console": "^5.0",
        "squizlabs/php_codesniffer": "^3.5"
    },
    "extra": {
        "hooks": {
            "pre-commit": "hooks/pre-commit.php"
        }
    }
}

      
      









Execute a instalação do composer novamente para que o arquivo seja copiado quando necessário.



Tudo está pronto, agora se você tentar comprometer o código com um estilo de código torto, o console git irá informá-lo sobre isso.



Como exemplo, vamos criar um arquivo MyClass.php na pasta src com o seguinte conteúdo.



<?php

namespace App;

class MyClass
{

    private $var1; private $var2;

    public function __construct() {
    }

    public function test() {

    }
}

      
      





Tentamos confirmar e obter erros de validação de código.



MBP-Admin:test admin$ git commit -am 'test'

Code Quality Tool
Fetching files
Running PHPLint
Checking code style with PHPCS
FILE: /Users/admin/projects/test/src/MyClass.php
----------------------------------------------------------------------
FOUND 5 ERRORS AFFECTING 5 LINES
----------------------------------------------------------------------
  8 | ERROR | [x] Each PHP statement must be on a line by itself
 10 | ERROR | [x] Opening brace should be on a new line
 13 | ERROR | [x] Opening brace should be on a new line
 15 | ERROR | [x] Function closing brace must go on the next line
    |       |     following the body; found 1 blank lines before
    |       |     brace
 16 | ERROR | [x] Expected 1 newline at end of file; 0 found
----------------------------------------------------------------------
PHPCBF CAN FIX THE 5 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

Time: 49ms; Memory: 6MB

In pre-commit line 53:
                                                
  There are PHPCS coding standards violations!                      

      
      





Viva, tudo funciona.



All Articles