Skip to content

Instantly share code, notes, and snippets.

@purdy
Created May 28, 2022 21:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save purdy/e76fe49a10cb26e5a01981c54726773d to your computer and use it in GitHub Desktop.
Save purdy/e76fe49a10cb26e5a01981c54726773d to your computer and use it in GitHub Desktop.
This is a file named pre-commit that we use in our repo's ~/.git/hooks to check our php code
#!/usr/bin/env php
<?php
// Credit: https://carlosbuenosvinos.com/write-your-git-hooks-in-php-and-keep-them-under-git-control/
require __DIR__ . '/../../vendor/autoload.php';
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process;
use Symfony\Component\Console\Application;
class CodeQualityTool extends Application
{
private $output;
private $input;
const PHP_FILES_IN_SRC = '/^src\/(.*)(\.php)$/';
const PHP_FILES_IN_CLASSES = '/^classes\/(.*)(\.php)$/';
public function __construct()
{
parent::__construct('Code Quality Tool', '1.0.0');
}
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>Check composer</info>');
$this->checkComposer($files);
$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</info>');
// if (!$this->codeStyle($files)) {
// throw new Exception(sprintf('There are coding standards violations!'));
// }
// $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>Checking code mess with PHPMD</info>');
// if (!$this->phPmd($files)) {
// throw new Exception(sprintf('There are PHPMD violations!'));
// }
// $output->writeln('<info>Running unit tests</info>');
// if (!$this->unitTests()) {
// throw new Exception('Fix the fucking unit tests!');
// }
$output->writeln('<info>Good job!</info>');
}
private function checkComposer($files)
{
$composerJsonDetected = false;
$composerLockDetected = false;
foreach ($files as $file) {
if ($file === 'composer.json') {
$composerJsonDetected = true;
}
if ($file === 'composer.lock') {
$composerLockDetected = true;
}
}
if ($composerJsonDetected && !$composerLockDetected) {
throw new Exception('composer.lock must be commited if composer.json is modified!');
}
}
private function extractCommitedFiles()
{
$output = array();
$rc = 0;
exec('git rev-parse --verify HEAD 2> /dev/null', $output, $rc);
$against = '945a25fc22a5ffd70947f98cf2f5263e664d3663';
if ($rc == 0) {
$against = 'HEAD';
}
exec("git diff-index --cached --name-status $against | egrep '^(A|M)' | awk '{print $2;}'", $output);
return $output;
}
private function phpLint($files)
{
$needle = '/(\.php)|(\.inc)|(\.module)$/';
$succeed = true;
foreach ($files as $file) {
if (!preg_match($needle, $file)) {
continue;
}
$process = new Process(array('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;
}
private function phPmd($files)
{
$needle = self::PHP_FILES_IN_SRC;
$succeed = true;
$rootPath = realpath(__DIR__ . '/../../');
foreach ($files as $file) {
if (!preg_match($needle, $file) || preg_match('/src\/AtrapaloLib\/ORM\/Doctrine\/DBAL\/Driver\/Adodb/', $file)) {
continue;
}
$process = new Process(['php', 'bin/phpmd', $file, 'text', 'controversial']);
$process->setWorkingDirectory($rootPath);
$process->run();
if (!$process->isSuccessful()) {
$this->output->writeln($file);
$this->output->writeln(sprintf('<error>%s</error>', trim($process->getErrorOutput())));
$this->output->writeln(sprintf('<info>%s</info>', trim($process->getOutput())));
if ($succeed) {
$succeed = false;
}
}
}
return $succeed;
}
private function unitTests()
{
$process = new Process(array('php', 'bin/phpunit'));
$process->setWorkingDirectory(__DIR__ . '/../..');
$process->setTimeout(3600);
$process->run(function ($type, $buffer) {
$this->output->write($buffer);
});
return $process->isSuccessful();
}
private function codeStyle(array $files)
{
$succeed = true;
foreach ($files as $file) {
$classesFile = preg_match(self::PHP_FILES_IN_CLASSES, $file);
$srcFile = preg_match(self::PHP_FILES_IN_SRC, $file);
if (!$classesFile && !$srcFile) {
continue;
}
$fixers = '-psr0';
if ($classesFile) {
$fixers = 'eof_ending,indentation,linefeed,lowercase_keywords,trailing_spaces,short_tag,php_closing_tag,extra_empty_lines,elseif,function_declaration';
}
$process = new Process(array('php', 'bin/php-cs-fixer', '--dry-run', '--verbose', 'fix', $file, '--fixers='.$fixers));
$process->setWorkingDirectory(__DIR__ . '/../../');
$process->run();
if (!$process->isSuccessful()) {
$this->output->writeln(sprintf('<error>%s</error>', trim($process->getOutput())));
if ($succeed) {
$succeed = false;
}
}
}
return $succeed;
}
private function codeStylePsr(array $files)
{
$succeed = true;
$needle = self::PHP_FILES_IN_SRC;
foreach ($files as $file) {
if (!preg_match($needle, $file)) {
continue;
}
$process = new Process(array('php', 'bin/phpcs', '--standard=PSR2', $file));
$process->setWorkingDirectory(__DIR__ . '/../../');
$process->run();
if (!$process->isSuccessful()) {
$this->output->writeln(sprintf('<error>%s</error>', trim($process->getOutput())));
if ($succeed) {
$succeed = false;
}
}
}
return $succeed;
}
}
$console = new CodeQualityTool();
$console->run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment