Данная инструкция показывает как автоматизировать проверку на code style в вашем php проекте.
Давайте посмотрим как будет выглядеть настройка в новом проекте.
Для этого в корне вашего проекта запускаем команду. Если у вас не установлен composer, то можете обратиться к официальной документации getcomposer.org
Сам обработчик можно написать на чем угодно, но так как статься про php то будем писать код на нем.
В данном примере код будет проходить 3 проверки:
— проверка на синтаксические ошибки
— проверка на PSR2 через code sniffer
PSR2 можно заменить на любой другой который поддерживает code sniffer. Список поддерживаемых стандартов можно увидеть введя команду
Для того чтобы код проверки запускался на pre commit хук нам необходимо положить файлик с кодом, который сделали в 3 пункте положить в папку .git/hooks/pre-commit. Это можно сделать вручную но куда удобнее это дело автоматизировать. Для этого нам нужно написать обработчик, который будет копировать этот файлик и повешать его на событие которые вызывается после composer install. Для этого делаем следующее.
В итоге файлкик composer.json примет следующий вид
Запускаем еще раз composer install чтобы файлик скопировался куда надо.
Все готово, теперь если вы попытаетесь закомитить код с кривым code style то git console вам об этом скажет.
В качестве примере давайте создадим в папке src файлик MyClass.php по следующим содержаением.
Пытаемся закомитить и получаем ошибки проверки кода.
Ура, всё работает.
Давайте посмотрим как будет выглядеть настройка в новом проекте.
Шаг 1 — Делаем инициализацию composer (у кого он уже настроен, пропускаем)
Для этого в корне вашего проекта запускаем команду. Если у вас не установлен composer, то можете обратиться к официальной документации getcomposer.org
composer init
Шаг 2 — Добавляем .gitignore
###> phpstorm ###
.idea
###< phpstorm ###
/vendor/
###> friendsofphp/php-cs-fixer ###
/.php_cs
/.php_cs.cache
###< friendsofphp/php-cs-fixer ###
Шаг 3 — Добавляем нужные библиотеки
composer require --dev friendsofphp/php-cs-fixer symfony/process symfony/console squizlabs/php_codesniffer
Шаг 4 — Добавляем обработчик хука
Сам обработчик можно написать на чем угодно, но так как статься про php то будем писать код на нем.
Создаем файлик в папочке 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();
В данном примере код будет проходить 3 проверки:
— проверка на синтаксические ошибки
— проверка на PSR2 через code sniffer
PSR2 можно заменить на любой другой который поддерживает code sniffer. Список поддерживаемых стандартов можно увидеть введя команду
vendor/bin/phpcs -i
Шаг 5 — Конфигурируем composer для реализации автозапуска проверки на pre-commit
Для того чтобы код проверки запускался на pre commit хук нам необходимо положить файлик с кодом, который сделали в 3 пункте положить в папку .git/hooks/pre-commit. Это можно сделать вручную но куда удобнее это дело автоматизировать. Для этого нам нужно написать обработчик, который будет копировать этот файлик и повешать его на событие которые вызывается после composer install. Для этого делаем следующее.
5.1 Создаем сам обработчик который будет копировать файлик pre-commit.php в папку хуков гита
Создаем файлик src/Composer/ScriptHandler.php
5.2 Настраиваем composer чтобы запускался обработчик
в composer.json добавляем следующую секцию
<?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
— перед install и update удаляется старый обработчикpost-install-cmd, post-update-cmd
— после install и update будет устанавливаться обработчик на pre commitВ итоге файлкик composer.json примет следующий вид
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"
}
}
}
Запускаем еще раз composer install чтобы файлик скопировался куда надо.
Все готово, теперь если вы попытаетесь закомитить код с кривым code style то git console вам об этом скажет.
В качестве примере давайте создадим в папке src файлик MyClass.php по следующим содержаением.
<?php
namespace App;
class MyClass
{
private $var1; private $var2;
public function __construct() {
}
public function test() {
}
}
Пытаемся закомитить и получаем ошибки проверки кода.
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!
Ура, всё работает.