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

Phpstan Disallowed Calls Laravel Package

spaze/phpstan-disallowed-calls

PHPStan rules to detect disallowed functions, methods, constants, namespaces, attributes, variables, and properties without running code. Catch leftover debug calls like var_dump() and enforce custom logging or API usage in production.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require --dev spaze/phpstan-disallowed-calls

With phpstan/extension-installer present, nothing else is needed. Otherwise add the include to phpstan.neon:

includes:
    - vendor/spaze/phpstan-disallowed-calls/extension.neon

Fastest first win: include a bundled ruleset (see docs/configuration-bundled.md for the full list — disallowed-dangerous-calls.neon and disallowed-loose-calls.neon are the usual starters) and run PHPStan once:

includes:
    - vendor/spaze/phpstan-disallowed-calls/extension.neon
    - vendor/spaze/phpstan-disallowed-calls/disallowed-dangerous-calls.neon
    - vendor/spaze/phpstan-disallowed-calls/disallowed-loose-calls.neon

Then add one hand-written rule so you learn the config shape, e.g. ban var_dump() with a helpful message:

parameters:
    disallowedFunctionCalls:
        -
            function: 'var_dump()'
            message: 'Use the app logger (or ray/dd in local only), never var_dump()'

Where to look next, in order: docs/custom-rules.md (all supported item types and keys — function/method/static calls, constants, enums, properties, namespaces, classes, attributes, keywords, superglobals), docs/configuration-bundled.md, then the docs/allow-*.md pages. The strict config schema (since v3.0) validates keys, so an unknown key fails loudly rather than silently doing nothing.

First real use case in a Laravel app: ban dd(), dump(), var_dump(), print_r(), ray() and env() outside config/, all in one config, and let CI catch the dd() someone forgot to delete.

Implementation Patterns

Start from bundled, then customize by re-declaring. A config entry replaces any previous entry for the same disallowed item (for calls, allowExceptParams values must match too). So to keep var_dump() disallowed everywhere except your own debug wrapper, don't try to "add" to the bundled rule — re-declare it with the extra allow directive and message:

parameters:
    disallowedFunctionCalls:
        -
            function: 'var_dump()'
            message: 'Use App\Support\Debug::dump()'
            allowIn:
                - 'App\Support\Debug::dump()'

Escape hatches are per-entry, and combine. allowIn (function/method names), path-based allows, parameter conditions (allowExceptParams family and their InAllowed variants), allowInInstanceOf, attribute-based allows, and PHPStan's own ignoreErrors. Directives are evaluated in order, and the item is allowed as soon as one matches. allowExcept* / disallow-in-style directives are not allow directives: when their pattern matches the item is reported, and later directives are never consulted.

Path-scope env() instead of banning it outright:

-
    function: 'env()'
    message: 'Read config via config(), env() only belongs in config/*.php'
    allowInPaths:
        - 'config/*'
        - 'tests/*'

Parameter-conditional rules for noisy APIs. Rather than banning DB::raw() (sometimes legitimately needed), ban the sharp edges or ban-with-exception on a specific argument, e.g. allow app() only when called with no arguments, or disallow ->whereRaw() unless the first argument is a constant string in your repository layer. Parameter conditions accept scalars, PHPDoc typeString ('foo'|'bar', array{}) since v3.0, and positions/names.

Wrapper-first workflow. Pick a small number of "never call directly" APIs — the framework logger facade, a mailer, a cache, exec(), shell_exec(), eval(), unserialize(), $_REQUEST/$_GET/$_POST via disallowedSuperglobals — and pair every disallowed entry with a sanctioned wrapper that is allowed via allowIn/allowInInstanceOf. This keeps the rule list short and gives developers a clear alternative, which is what actually stops the bad call.

Type-hint carve-outs. For disallowedClasses/disallowedNamespaces, allow in type-hint positions (param/return types) where a boundary class is unavoidable, while still blocking new/static usage. Applies only to those two key types.

Legacy codebase rollout: run with --generate-baseline once, commit the baseline, and keep the rule set as-is; new violations fail CI. Alternatively use default error identifiers in ignoreErrors for a handful of known files. Note the baseline approach hides re-introduced violations in the same lines, so prefer wrapper + allowIn* where you can.

Audit mode (no other rules). Per docs/phpstan-custom-ruleset.md, run this extension with a minimal ruleset against vendor/ to audit a third-party package for eval, exec, superglobals, etc., without adopting a full PHPStan level:

vendor/bin/phpstan analyse -c phpstan-disallowed-only.neon vendor/acme/package

Keep disable_functions / disable_classes in sync. Use the provided generator (docs/disallow-disabled-functions-classes.md) to produce a config snippet from production's php.ini, so code that calls a disabled function is flagged in CI rather than fatalling in prod.

CI integration. Standard vendor/bin/phpstan analyse in the pipeline is enough — violations are ordinary PHPStan errors, so exit code 1 fails the build. Add --error-format=github for inline annotations. Use errorTip (a string or, since v4.8, an array) to put the "why" next to the error instead of burying it in docs.

Typical Laravel rule set to build toward (each with an allowed wrapper/config location):

Disallow Allow where
dd(), dump(), ray(), var_dump(), print_r() App\Support\Debug only
env() config/*, tests/*
$_REQUEST, $_GET, $_POST, $_COOKIE request/service layer only
exec(), shell_exec(), eval(), unserialize(), phpinfo() nowhere (or a hardened service)
Log:: facade (generic logger) your logging wrapper via allowIn
DB::raw(), ->whereRaw() repository classes via allowInInstanceOf

Gotchas and Tips

  • Case sensitivity is split: functions, methods, classes and namespaces match case-insensitively (print_r catches print_R); constants, paths and filenames are case-sensitive. Path typos in allowInPaths silently never match.
  • Unknown functions are no longer reported (v4.9.2). If a function has no reflection (extension not loaded, polyfill, typo'd name), it won't be flagged even if configured. Same as methods already. So a rule for sodium_* won't fire on a machine where the extension is missing — don't treat this as a security control (the README is explicit: it's a second pair of eyes, obfuscatable by design).
  • Messages get normalized. Since v3.0 there's no automatic "because reasons", and a full stop is appended unless the message already ends with ., ? or !. Don't put trailing punctuation logic in your head — and don't write ignoreErrors regexes anchored on a period you didn't write.
  • Config is a strict structure. Typos now error out instead of being ignored — good, but it means copy-pasted snippets from old blog posts (v2 era) may fail validation. Migrate key names from the current docs/.
  • Replacement, not merging. Adding a second entry for var_dump() replaces the first (and the bundled one); the later entry wins, so restate the message and every allow directive you still want. Since v3.0 you can add multiple rules for the same call to get different messages per parameter set — but that changed the pre-3.0 "first rule wins by order" behaviour.
  • else if with a space throws. It parses as else + if; disallow elseif instead. Also, parameter matching inside (...) doesn't work for control structures: you can disallow all declare() but cannot re-allow declare(strict_types=1).
  • Enums route through constants. disallowedEnums uses the constant machinery internally; a v4.7.1 fix removed template types from the declaring-class name in messages for PHPStan 2.1.38+, so message-matching ignoreErrors regexes may need adjusting after PHPStan upgrades.
  • Type-hint allowance is narrow: only disallowedNamespaces and disallowedClasses respect the type-hint-position allow; asking for the same on constants or calls won't do anything.
  • Debugging a non-firing rule: confirm extension.neon is actually included (phpstan --debug / -v shows included configs), re-run with --clear-result-cache after config edits, and check the rule against a deliberately bad file in an analyzed path. If the file isn't matched by paths/excludePaths, PHPStan never sees it — the most common "rule doesn't work" cause.
  • Extension points: the documented ones are the allow* directives, errorTip, custom error identifiers, and the bundled-config generator. If you need detection of a construct not covered in tests/src, that's an upstream issue/PR — the README says plainly that untested constructs are probably not detected.
  • Keep it cheap and visible: run the strict set in CI and the same set locally via a Composer script or a make lint target so dd() is caught before commit, not after review.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route