Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Static Review Laravel Package

sjparkinson/static-review

Static Review is a PHP tool that runs configurable static-analysis checks on files staged for commit, helping catch code-quality issues before they reach the repository. It integrates with linters like PHP_CodeSniffer and PHP Mess Detector and fits into Git hooks or custom workflows.

Deep Wiki
Context7

Getting Started

sjparkinson/static-review is a small framework for running checks against the files a Git hook is about to act on. You define a set of reviews in a PHP config file, and the package's CLI binary runs those reviews against the staged file list and exits non-zero if any review reports an error. Think of it as a pluggable dispatcher for pre-commit, commit-msg, and pre-push hooks rather than a linter itself.

Install it as a dev dependency:

composer require --dev sjparkinson/static-review

The first place to look is the project's .static-review.php file — it's the whole configuration surface. After that, read src/Review/AbstractReview.php (the extension point for custom checks), src/Review/ReviewCollection.php, src/Reporter/Reporter.php, and src/VersionControl/GitVersionControl.php.

A minimal config that blocks commits containing PHP syntax errors and refuses to run when the working tree has unstaged edits:

<?php
// .static-review.php
use StaticReview\Reporter\Reporter;
use StaticReview\Review\ReviewCollection;
use StaticReview\Review\Generic\UnstagedFilesReview;
use StaticReview\Review\PHP\PhpLintReview;
use StaticReview\VersionControl\GitVersionControl;

$reporter = new Reporter();
$git      = new GitVersionControl();

$reviews = new ReviewCollection();
$reviews->add(new UnstagedFilesReview($reporter, $git));
$reviews->add(new PhpLintReview($reporter));

return $reviews;

Wire the binary into a hook (the binary takes the hook name as its argument):

cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
exec ./vendor/bin/static-review pre-commit
EOF
chmod +x .git/hooks/pre-commit

First use case: catch a missing semicolon or unbalanced brace before the commit is written, instead of in CI twenty minutes later. Run ./vendor/bin/static-review pre-commit manually at any time to see exactly what the hook sees.

Implementation Patterns

One config, per-hook branching. The hook name is passed as the CLI argument, so the same config can build different collections:

$hook = $_SERVER['argv'][1] ?? 'pre-commit';

$this->reviews->add(new PhpLintReview($reporter));

if ($hook === 'pre-push') {
    $this->reviews->add(new PhpUnitReview($reporter)); // full test run only on push
}

Keep pre-commit fast (lint, whitespace, forbidden words) and push expensive checks (test suites, static analysis) to pre-push.

Custom reviews are the main integration point. Extend AbstractReview, implement canReview() as a cheap filter and review() as the actual work:

<?php
namespace App\Reviews;

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

class NoDebugStatementsReview extends AbstractReview
{
    public function canReview(FileInterface $file)
    {
        return in_array($file->getExtension(), ['php', 'phtml'], true);
    }

    public function review(FileInterface $file)
    {
        $contents = file_get_contents($file->getPathname());

        if (preg_match('/\b(var_dump|dd|dump)\s*\(/', $contents, $match)) {
            $this->reporter->addReview($this, $file, sprintf('Found %s()', $match[1]));
        }

        return $this;
    }
}

Register custom reviews with a PSR-4 autoload entry in composer.json, run composer dump-autoload, then add them to the collection.

Wrap external tools instead of reimplementing them. For phpcs, php-cs-fixer, eslint, or phpstan, write a thin review that shells out via Symfony\Component\Process\Process, passes $file->getPathname(), and reports non-zero exit codes through the reporter. This keeps the config declarative and lets you swap the underlying tool later.

Share a base class for tool wrappers. A ProcessReview abstract with protected function commandFor(FileInterface $file): array collapses each external tool into about ten lines.

Mirror the same reviews in CI. The hook is a convenience; run the identical command in CI (for example, ./vendor/bin/static-review pre-commit after checking out) so a developer with --no-verify doesn't silently bypass the checks.

Gotchas and Tips

  • Archived since 2017. Last release was November 2017, before PHP 7.2 even. Expect friction on PHP 8.x — return-type mismatches, deprecated dynamic properties, and old symfony/process / symfony/console constraints. Pin PHP in your dev environment or vendor a fork rather than fighting it in a modern stack.
  • Hooks are not version-controlled. .git/hooks/ is per-clone and never committed. Provide an installer script (a Composer post-install-cmd or a bin/install-hooks script) and document it in the README, otherwise half the team never gets the checks.
  • Partial staging is the classic failure mode. With git add -p, the worktree differs from the index, so a linter reading the file on disk checks code that isn't being committed. Keep UnstagedFilesReview first in the collection or explicitly review the index content.
  • canReview() runs for every file. Keep it to extension/name checks; no I/O, no process spawning. A slow canReview() on a 500-file commit is immediately noticeable.
  • Exit codes matter, not chatter. The commit is blocked by reporter errors; notices and warnings are advisory. Verify which level your review emits — a check that "prints a problem" but doesn't error is worse than no check.
  • Use the FileInterface object, not raw paths. The wrapper carries the repo-relative path and extension the rest of the framework relies on. file_get_contents($file->getPathname()) is fine; hard-coding getcwd() paths is not.
  • Subprocess cwd and environment. Tools invoked from a hook inherit Git's working directory and a possibly minimal PATH (GUI clients especially). Pass an explicit cwd to Process and use absolute binary paths if you see "command not found" only when committing from an IDE.
  • Debugging. Run the binary directly with the hook name to reproduce; there is no meaningful -v in the 2017-era CLI, so add temporary fwrite(STDERR, ...) calls inside review() and remove them. Also confirm the hook script itself is executable — a silently no-op pre-commit is almost always chmod +x.
  • Windows/CRLF. Shell-shebang hooks fail on Windows; use a .bat/.cmd hook or run the binary through php. Line-ending reviews will also fight core.autocrlf.
  • --no-verify is always available. Treat hooks as a fast feedback loop, not a security boundary; CI must enforce the same rules.
  • Extension points to remember: custom AbstractReview subclasses, a custom Reporter implementation, and an alternate VersionControlInterface adapter (the package ships Git; others require writing one). Everything else is plain PHP composition in .static-review.php, so the config file itself is the cleanest place to inject project-specific paths and options.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource