Writing PHP Git Hooks with Static Review

Share this article

If you’ve been using Git for more than a short length of time, you’ll hopefully have heard of Git hooks. If not, Git hooks, as Andrew Udvare introduced here on SitePoint back in August of last year, are scripts which run, based on certain events in the Git lifecycle, both on the client and on the server.

Hook illustration

There are hooks for pre- and post-commit, pre- and post-update, pre-push, pre-rebase, and so on. The sample hooks are written in Bash, one of the Linux shell languages. But they can be written in almost any language you’re comfortable or proficient with.

As Tim Boronczyk pointed out in September 2013, there are a wide range of meaningful purposes which hooks can be put to. These include linting, spell-checking commit messages, checking patches against accepted coding standards, running composer, and so on.

Now, PHP’s a great language, don’t get me wrong, but it’s not necessarily the best language for shell scripting. Admittedly, it’s gotten a lot better than it once was. But compared to languages such as Bash, Python, and Ruby, it hasn’t been quite as suitable for creating Git hooks; that is, until now.

Thanks to Static Review, by Samuel Parkinson, you can now write Git hooks with native PHP, optionally building on the existing core classes. In today’s post, I’m going to give you a tour of what’s on offer, finishing up by writing a custom class to check for any lingering calls to var_dump().

Installing the Library

Like most, if not all modern PHP libraries and packages, StaticReview is installable via Composer. To install it in your project, run composer require sjparkinson/static-review in the root of your project. With that, let’s get going.

A Working Example

Like any code, the best way to understand it is to step through a real example, such as the one below, taken from the StaticReview project repository.

Firstly, like all shell scripts, it defines what will run the script. In this case, it’s PHP. After that, the Composer autoload script is included, so that the script has access to all the required classes, as well as a warning, should the autoload script not be available.

#!/usr/bin/env php
<?php

$included = include file_exists(__DIR__ . '/../vendor/autoload.php')
    ? __DIR__ . '/../vendor/autoload.php'
    : __DIR__ . '/../../../autoload.php';

if (! $included) {
    echo 'You must set up the project dependencies, run the following commands:' . PHP_EOL
       . 'curl -sS https://getcomposer.org/installer | php' . PHP_EOL
       . 'php composer.phar install' . PHP_EOL;

    exit(1);
}

Next, as with most modern PHP scripts, all of the required classes are imported, and three variables are initialized. These are: a Reporter, a CLImate, and a GitVersionControl object. The Reporter object provides information about the hook, whether the hook succeeded or failed. The CLImate object makes outputting colored text and text formats simple. And the GitVersionControl object simplifies interactions with Git.

// Reference the required classes and the reviews you want to use.
use League\CLImate\CLImate;
use StaticReview\Reporter\Reporter;
use StaticReview\Review\Composer\ComposerLintReview;
use StaticReview\Review\General\LineEndingsReview;
use StaticReview\Review\General\NoCommitTagReview;
use StaticReview\Review\PHP\PhpLeadingLineReview;
use StaticReview\Review\PHP\PhpLintReview;
use StaticReview\StaticReview;
use StaticReview\VersionControl\GitVersionControl;

$reporter = new Reporter();
$climate  = new CLImate();
$Git      = new GitVersionControl();

Next, it creates a new StaticReview object, and passes in the types of reviews to run in the hook. In the case below, it adds five. These reviews:

  • Checks if the file contains any CRLF line endings
  • Checks if the set file starts with the correct character sequence
  • Checks if the file contains NOCOMMIT.
  • Checks PHP files using the built-in PHP linter, php -l.
  • Checks if the composer.json file is valid.

Then, it tells the review to check any staged files.

$review = new StaticReview($reporter);

// Add any reviews to the StaticReview instance, supports a fluent interface.
$review->addReview(new LineEndingsReview())
       ->addReview(new PhpLeadingLineReview())
       ->addReview(new NoCommitTagReview())
       ->addReview(new PhpLintReview())
       ->addReview(new ComposerLintReview());

// Review the staged files.
$review->review($Git->getStagedFiles());

Similar to other forms of validation, if issues are reported by any of the review classes, each issue is printed out to the terminal, in red, preceded by an , and the commit does not complete. However, if there are no problems, then a success message, ✔ Looking good. Have you tested everything?, is printed out, and the commit is allowed to complete.

// Check if any matching issues were found.
if ($reporter->hasIssues()) {
    $climate->out('')->out('');
    foreach ($reporter->getIssues() as $issue) {
        $climate->red($issue);
    }
    $climate->out('')->red('✘ Please fix the errors above.');
    exit(1);
} else {
    $climate->out('')->green('✔ Looking good.')->white('Have you tested everything?');
    exit(0);
}

So far, nothing you’d be unfamiliar with, if you’ve worked with Git hooks before. But how, specifically, does a review class operate? Every Review class extends AbstractReview, which implements ReviewInterface. This interface requires two methods to be implemented: canReview(), and review().

canReview, as the name implies, determines if a review can be run, and review does the actual review. Take ComposerLintReview.php as an example, which you can see below. canReview() checks if the file being reviewed is called composer.json. If so, review() can be called.

This then creates a command which invokes Composer’s validate functionality on composer.json, and passes that to the getProcess method, implemented in AbstractReview, running the process. If the process is not successful, an error message is created, and set on the Reporter object, by passing it to a call to Reporter’s error() method, as well as the file which was reviewed.

public function canReview(FileInterface $file)
{
    // only if the filename is "composer.json"
    return ($file->getFileName() === 'composer.json');
}

public function review(ReporterInterface $reporter, FileInterface $file)
{
    $cmd = sprintf('composer validate %s', $file->getFullPath());
    $process = $this->getProcess($cmd);
    $process->run();

    if (! $process->isSuccessful()) {
        $message = 'The composer configuration is not valid';
        $reporter->error($message, $this, $file);
    }
}

In a nutshell, that’s all that’s required to create a Git hook to validate files on one of the events in the Git lifecycle.

A Custom Review

If you browse under vendor/sjparkinson/static-review/src/Review/, you’ll see there are quite a number of pre-packaged Review classes available. They cover Composer, PHP, and general purpose reviews. But what if we want to create one ourselves, one to suit our specific use case?

What if we’re concerned that we might leave var_dump statements in our code? We wouldn’t, right? But hey, never hurts to be sure, as old habits can sometimes die hard. So what would a custom review look like? Let’s work through one and find out.

First, we’ll create a new directory structure to store our PSR-4 compliant code. Use the following command in the project’s root directory.

mkdir -p src/SitePoint/StaticReview/PHP

Then, in composer.json, add the following to the existing configuration, and run composer dumpautoload:

"autoload": {
  "psr-4": {
    "SitePoint\\": "src/SitePoint/"
  }
}

This will update Composer’s autoloader to also autoload our new namespace. With that done, create a new class, called VarDumpReview.php in src/SitePoint/StaticReview/PHP. In it, add the following code:

<?php

namespace SitePoint\StaticReview\PHP;

use StaticReview\File\FileInterface;
use StaticReview\Reporter\ReporterInterface;
use StaticReview\Review\AbstractReview;

class VarDumpReview extends AbstractReview
{
    public function canReview(FileInterface $file)
    {
        $extension = $file->getExtension();
        return ($extension === 'php' || $extension === 'phtml');
    }

    public function review(ReporterInterface $reporter, FileInterface $file)
    {
        $cmd = sprintf('grep --fixed-strings --ignore-case --quiet "var_dump" %s', $file->getFullPath());
        $process = $this->getProcess($cmd);
        $process->run();

        if ($process->isSuccessful()) {
            $message = 'A call to `var_dump()` was found';
            $reporter->error($message, $this, $file);
        }
    }
}

Based off of PhpLintReview.php and ComposerLintReview.php, canReview checks if, based on the extension, the file being checked is a PHP file. If so, review then uses grep to scan the file for any references to var_dump.

If any are found, an error is registered, which tells the user that a call to var_dump was found, and the commit fails. If you were to run it, you could expect output as in the screenshot below.

StaticReview failure

Creating the Hook

There’s just one last step to go, which is to create a Git hook from our hook class. In a new directory Hooks in my project root, I’ve copied the hook code we worked through at the beginning of the article, making one small change.

I added our new Review file to the list of Reviews, as follows:

$review->addReview(new LineEndingsReview())
       ->addReview(new PhpLeadingLineReview())
       ->addReview(new NoCommitTagReview())
       ->addReview(new PhpLintReview())
       ->addReview(new ComposerLintReview())
       ->addReview(new VarDumpReview());

With that done, create a pre-commit hook, by running the following command.

./vendor/bin/static-review.php hook:install hooks/example-pre-commit.php .Git/hooks/pre-commit

And with that, if you look in .git/hooks, you’ll now see a symlink created from pre-commit, to our new hooks file. To test the hook, make a call to var_dump() in any PHP file, stage the file, and attempt to commit it.

You shouldn’t even be allowed to create a commit message before the error message shows. If you then update the file to remove the call to var_dump(), you should see a success message, before being able to add a commit message as in the image below.

StaticReview success

Wrapping Up

And that’s all it takes to create simple or powerful Git hooks, using one of the most versatile languages around, PHP. I only came across Static Review thanks to the ever vigilant Bruno Skvorc. But I’m really thankful he suggested checking it out.

With it, I can now do one more, ever important development task, using my favorite software development language – PHP. Are you already using Static Review? If so, share your experience in the comments. I’m keen to know how people more experienced than myself are using it.

Frequently Asked Questions (FAQs) about PHP Git Hooks with Static Review

What is the main purpose of using PHP Git Hooks with Static Review?

PHP Git Hooks with Static Review are primarily used to automate certain tasks in the development process. They are scripts that Git executes before or after events such as commit, push, and receive. Static Review is a tool that allows you to run checks on your code before it is committed. This helps in maintaining code quality, enforcing coding standards, and preventing potential issues before they become a part of the codebase.

How do I install PHP Git Hooks with Static Review?

To install PHP Git Hooks with Static Review, you need to have Composer, a dependency management tool for PHP. You can install it by running the command composer require --dev sjparkinson/static-review. After installation, you can configure it to suit your project’s needs.

Can I customize PHP Git Hooks with Static Review?

Yes, PHP Git Hooks with Static Review are highly customizable. You can write your own reviewers or use the ones provided by Static Review. You can also configure which files or directories the reviewers should check, and which Git events should trigger the reviewers.

What are some common issues I might encounter when using PHP Git Hooks with Static Review?

Some common issues include conflicts with other Git hooks, problems with installation or configuration, and reviewers not working as expected. Most of these issues can be resolved by checking the documentation or seeking help from the community.

How can PHP Git Hooks with Static Review improve my development workflow?

PHP Git Hooks with Static Review can automate many tasks in your development workflow, such as checking coding standards, running tests, and checking for potential issues. This not only saves time but also helps in maintaining a high-quality codebase.

Are there any alternatives to PHP Git Hooks with Static Review?

Yes, there are several alternatives to PHP Git Hooks with Static Review, such as pre-commit, overcommit, and husky. These tools also provide Git hooks and static code analysis, but they may have different features and support different programming languages.

Can I use PHP Git Hooks with Static Review in a team environment?

Yes, PHP Git Hooks with Static Review can be used in a team environment. You can share the configuration and reviewers with your team members, ensuring that everyone follows the same coding standards and practices.

How do I troubleshoot issues with PHP Git Hooks with Static Review?

If you encounter issues with PHP Git Hooks with Static Review, you can check the documentation, seek help from the community, or debug the issue yourself. The error messages provided by Git and Static Review can often give you clues about what’s going wrong.

Can I use PHP Git Hooks with Static Review with other development tools?

Yes, PHP Git Hooks with Static Review can be used with other development tools. For example, you can use it with continuous integration tools to run checks on your code automatically whenever you make a commit.

What are the system requirements for PHP Git Hooks with Static Review?

PHP Git Hooks with Static Review requires PHP 5.4 or later and Composer. It also requires Git, as it relies on Git hooks to trigger the reviewers.

Matthew SetterMatthew Setter
View Author

Matthew Setter is a software developer, specialising in reliable, tested, and secure PHP code. He’s also the author of Mezzio Essentials (https://mezzioessentials.com) a comprehensive introduction to developing applications with PHP's Mezzio Framework.

BrunoSgitgit hooksgit hooks introductiongit hooks tutorialhooksOOPHPPHPqaquality assuranceTestingversion control
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week