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

Grumphp Shim Laravel Package

phpro/grumphp-shim

A lightweight Composer shim that provides GrumPHP as a single PHAR, so you can run code quality checks via vendor/bin/grumphp.phar without installing all of GrumPHP's dependencies yourself. Ideal for fast, simple Git hook automation.

View on GitHub
Deep Wiki
Context7

Getting Started

The shim is a distribution mechanism, not a different tool. It installs GrumPHP as a bundled PHAR so its own dependencies (Symfony components, php-parser, etc.) never land in your project's vendor/ and never collide with Laravel's pinned versions. Everything you configure and run is stock GrumPHP — so the first place to look is not this repo's README (which is intentionally three lines), but the upstream docs at phpro/grumphp. Treat this repo as "GrumPHP, installed differently."

1. Install (dev only):

composer require --dev phpro/grumphp-shim

You get one binary: vendor/bin/grumphp.phar. There is no vendor/bin/grumphp — this is the single most common first stumble.

2. Confirm it runs:

vendor/bin/grumphp.phar --version
php -m | grep -i phar   # the phar extension must be enabled

If --version errors out with a PHP constraint message, your PHP runtime is outside the range the PHAR was built for — bump the shim (composer update phpro/grumphp-shim) before debugging anything else.

3. Drop in a config. GrumPHP looks for grumphp.yml (or grumphp.yml.dist) in the directory you run it from. A minimal Laravel-flavoured starting point:

# grumphp.yml
grumphp:
    tasks:
        git_commit_message:
            max_subject_width: 80
        git_conflict: ~
        phpcs:
            standard: PSR12
            triggered_by: [php]
        phpstan:
            level: 5
            triggered_by: [php]

Then commit the file and run once by hand:

vendor/bin/grumphp.phar run

4. Wire up the hooks. The whole point is the pre-commit gate:

vendor/bin/grumphp.phar git:init

This writes .git/hooks/pre-commit (and commit-msg if you configured git_commit_message). Open the generated hook file and verify the path inside points at vendor/bin/grumphp.phar and not vendor/bin/grumphp.

First real use case: a Laravel app where phpcs/phpstan/phpunit already exist in vendor/ but GrumPHP couldn't be installed because its dependency tree fought with the app's. The shim sidesteps that entirely — the project keeps its own tool versions, the PHAR supplies GrumPHP itself.


Implementation Patterns

Composer scripts as the team interface. Don't ask developers to remember the .phar suffix; alias it:

"scripts": {
    "grumphp": "vendor/bin/grumphp.phar run",
    "grumphp:init": "vendor/bin/grumphp.phar git:init",
    "post-install-cmd": ["@grumphp:init"],
    "post-update-cmd": ["@grumphp:init"]
}

Now composer grumphp is the daily command, and fresh clones get hooks automatically on composer install. Guard the post-install script for CI (--no-dev installs skip it anyway, but hook writes are meaningless in a throwaway container).

Split the task set by context. Keep the pre-commit gate fast and deterministic (lint + style + static analysis on changed files), and let CI run the expensive suite. GrumPHP exposes task selection per run:

# local hook / fast feedback
vendor/bin/grumphp.phar run --tasks=phpcs,phpstan

# CI: everything, no prompts, no ANSI
vendor/bin/grumphp.phar run -n --no-ansi

Then keep phpunit out of the hook config entirely and call it from CI, or gate it behind --tasks=phpunit on demand.

Pint instead of PHP-CS-Fixer. GrumPHP's phpcsfixer task expects PHP-CS-Fixer's CLI, not Pint's. Two clean options: use the phpcs task with standard: PSR12 (works everywhere), or run Pint through a shell task so you keep Laravel's opinionated ruleset:

grumphp:
    tasks:
        shell:
            scripts:
                - ["vendor/bin/pint", "--test", "--dirty"]
            triggered_by: [php]

The --test flag is essential — a formatter that rewrites files mid-commit is a footgun.

Custom metadata + custom tasks. GrumPHP auto-detects tool versions from composer.json/composer.lock; with a PHAR this detection can miss things, so be explicit where it matters:

grumphp:
    tasks:
        phpstan:
            executable: ["vendor/bin/phpstan"]
            memory_limit: 1G

This is the single most valuable habit with the shim: always point executable at your project's binary rather than letting the task guess.

Monorepo / multi-service uniformity. Because the PHAR is self-contained, you can ship the identical grumphp.yml (or a .dist variant) to every repo and version the shim in composer.json to ^2.25 so all teams run the same GrumPHP build. No per-project dependency resolution drift.

Secrets and standards as a merge gate. git_blacklist catches hardcoded credentials before they ever hit a commit object:

git_blacklist:
    keywords: ["DB_PASSWORD=", "AWS_SECRET", "BEGIN RSA PRIVATE KEY"]
    triggered_by: [php, env, yml]

Bypass discipline. Document git commit --no-verify as an emergency-only escape hatch, and have CI re-run grumphp.phar run so a bypass never reaches main. The hook is a convenience; CI is the actual gate.


Gotchas and Tips

The binary is grumphp.phar. Copy-pasted tutorials, editor integrations, IDE plugins, and Docker entrypoints all reference vendor/bin/grumphp. Every one of them needs the suffix changed. When a "command not found" appears in a hook, this is the cause roughly every time.

The PHAR bundles GrumPHP, not your tools. phpcs, phpstan, psalm, phpunit, pint are not inside the archive. They must exist in your project (or be on PATH). This is the trade-off: you avoided GrumPHP's dependency conflicts, but you still own your analyser versions — which is exactly what you want, since PHPStan needs your app's autoloader anyway.

Custom tasks and service extensions may not autoload. The PHAR runs with its own autoloader; classes from your project's vendor/autoload.php aren't automatically visible to it. If a custom task, metadata class, or config extension isn't found, don't fight it — implement the behaviour as a shell task or as a plain script the task invokes, and reserve custom PHP tasks for projects that install upstream phpro/grumphp directly. Test this before designing around it.

PHP upgrade = shim upgrade. The archive carries a compiled-in PHP requirement and a disabled requirements checker (recent releases explicitly patched this for newer PHP). Bumping the interpreter on your base image or Herd/Valet setup without bumping the shim is a classic "worked yesterday" failure. Pin it in composer.json and let Renovate/Dependabot move it alongside PHP.

Staged vs working tree. Inside the pre-commit hook, GrumPHP inspects the staged set; run manually, it inspects the working tree. Results legitimately differ. When debugging a hook-only failure, reproduce with an explicit commit or inspect what git diff --cached actually contains.

Hooks go stale. git:init writes files into .git/hooks, which is not version controlled. New clones have none until composer install runs the post-install script — and git:init is idempotent, so re-running it is safe and a good "fix it" reflex when hooks behave oddly.

Debugging ladder, in order:

  1. vendor/bin/grumphp.phar run --no-ansi -vvv — full task output and command lines.
  2. vendor/bin/grumphp.phar run --tasks=phpstan — isolate the failing task.
  3. Check the exact command string GrumPHP built (verbose output shows it); 90% of task failures are a wrong executable path, wrong working directory, or a missing triggered_by extension.
  4. git commit --no-verify to confirm the app code is fine and the failure is purely the gate.

Config discovery quirks. Run from the repo root — GrumPHP resolves grumphp.yml relative to CWD, not to the binary. Use --config=path/to/grumphp.yml in monorepos or when running from a subdirectory, and remember grumphp.yml.dist only applies when no local grumphp.yml exists (which is why teams commit .dist and gitignore the local file for per-developer overrides).

Performance. PHAR startup plus multiple analysers on every commit gets slow on large codebases. Mitigations: triggered_by so tasks only run when relevant file types are staged, ignore_patterns for vendor/, storage/, bootstrap/cache/, node_modules/, and moving phpunit/full PHPStan analysis to CI. A pre-commit gate that takes 45 seconds will get bypassed — treat speed as a feature, not an optimisation.

phar.readonly is irrelevant here. That ini setting only blocks building PHARs; running one needs only the phar extension compiled in (default on virtually all distros, but disabled in some hardened images — php -m | grep phar settles it).

Extension points worth knowing: shell (escape hatch for any tool), git_commit_message (conventional-commit enforcement), git_conflict, git_branch_name, composer/composer_normalize, securitychecker variants, and per-task ignore_patterns + triggered_by. Combined with explicit executable paths, these cover nearly every Laravel quality-gate requirement without custom PHP.

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.
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support