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 Safe Rule Laravel Package

thecodingmachine/phpstan-safe-rule

PHPStan rule that detects calls to unsafe core PHP functions and suggests the thecodingmachine/safe variants, which throw exceptions instead of returning false on error. Helps catch unsafe function usage during static analysis.

View on GitHub
Deep Wiki
Context7

Getting Started

This rule is a companion to thecodingmachine/safe — it is worthless on its own. Install them as a pair, in the same commit:

composer require thecodingmachine/safe          # runtime: your app calls Safe\* functions
composer require --dev thecodingmachine/phpstan-safe-rule

Safe belongs in require (production code imports Safe\ functions); the rule is analyser-only and must stay in require-dev.

If you already have phpstan/extension-installer, the rule's extension.neon is picked up automatically — no config change needed. Otherwise register it explicitly in phpstan.neon / phpstan.neon.dist:

includes:
    - vendor/larastan/larastan/extension.neon
    - vendor/thecodingmachine/phpstan-safe-rule/extension.neon

parameters:
    level: 6
    paths:
        - app
        - routes

Requirements as of v1.4.x: PHP 8.1+ and PHPStan 2.x (PHPStan 1.x support was dropped in v1.3/v1.4 — if you're still pinned to PHPStan 1, stay on v1.2 and accept no further fixes).

Then run the analyser you already run:

vendor/bin/phpstan analyse --memory-limit=1G

You'll get findings like "Function file_get_contents is unsafe, use Safe\file_get_contents instead" pointing at each call site. First thing to do after the first run: verify the gate actually fires. Deliberately add $x = json_decode($s); to a scratch file and confirm it's flagged. A mis-ordered/missing includes: produces a silent green build that looks identical to "codebase is clean" — the single most dangerous failure mode of this package.

First real use case: run it on app/ only, generate a baseline, and treat the baseline as your migration backlog (see below).

Implementation Patterns

Baseline-first rollout on a legacy Laravel app. Never try to fix thousands of findings in one PR. Generate a baseline, commit it, and gate new code only:

vendor/bin/phpstan analyse --generate-baseline
git add phpstan-baseline.neon
includes:
    - phpstan-baseline.neon
    - vendor/thecodingmachine/phpstan-safe-rule/extension.neon

Then burn down the baseline per module: grep -c 'safe' phpstan-baseline.neon per path gives you a countable backlog ("checkout service: 14 unsafe calls, reporting jobs: 31"), which is a far better sprint ticket than "improve error handling".

Call the Safe variants with function imports. Inside namespaced Laravel code, an unqualified file_get_contents() is a global fallback call and gets flagged. Import explicitly — this is also what reviewers should see in diffs:

use function Safe\file_get_contents;
use function Safe\json_decode;
use function Safe\preg_match;

public function import(string $path): array
{
    $raw = file_get_contents($path);   // throws instead of returning false
    $data = json_decode($raw, true);   // throws on malformed JSON
    if (preg_match('/^v(\d+)/', $data['tag'], $m) === 1) { ... }
    return $data;
}

Use JSON_THROW_ON_ERROR as the sanctioned opt-out. The rule already understands json_decode($s, true, 512, JSON_THROW_ON_ERROR) and the named-argument form (flags: JSON_THROW_ON_ERROR, supported since v1.4.1). That's often a smaller diff than switching to Safe\json_decode, and it keeps native error semantics. Pick one convention per codebase so the static analysis doesn't flip-flop.

Exploit the bundled type inference instead of casting. The extension ships dynamic return types for Safe\json_decode and Safe\preg_replace_callback, and mirrors PHPStan's preg_match special-casing so $matches gets typed from a literal pattern (v1.4.0+). That means:

use function Safe\preg_match;

if (preg_match('/(?<id>\d+)/', $line, $m) === 1) {
    $id = $m['id'];   // string — no cast, no isset dance
}

This is the main reason to prefer Safe\ over hand-rolled try/catch wrappers: you keep upstream inference. It only works with literal patterns — a dynamically built regex degrades $matches back to a generic ?array.

Replace new DateTime / new DateTimeImmutable (detected since v1.0) with the Safe\ variants when parsing untrusted input — Laravel's Carbon::parse() ultimately sits on the same behaviour, so treat form/API date strings as the priority targets.

Gate changed files only, in a pre-commit hook — the pragmatic middle ground before the baseline is empty:

vendor/bin/phpstan analyse --no-progress $(git diff --name-only --cached --diff-filter=ACM -- '*.php')

Suppressions carry an owner. Use identifier-scoped ignores rather than path-wide ones, and count them in CI:

parameters:
    ignoreErrors:
        - identifier: <the identifier shown in the error message>
          path: app/Legacy/CsvImporter.php
          count: 2

Run --error-format=json once to see the exact identifier string; the identifier was fixed in v1.4.0, so anything written against older versions may not match.

Gotchas and Tips

  • Silent no-op is the default failure mode. Wrong includes:, a phpstan.neon overriding phpstan.neon.dist, or a stale result cache all mean zero findings and a false sense of security. Add an intentional unsafe call in a CI smoke test (or a tests/static-analysis/fixtures path analysed in a separate job) so the pipeline proves the rule is loaded.
  • PHPStan 1.x is unsupported in current releases. v1.3.0 added PHPStan 2 and dropped 1.x; v1.4 dropped EOL PHP. Pin both phpstan/phpstan and this package, and upgrade them together.
  • It cannot see through Laravel's wrappers. The rule matches direct calls to native functions. Storage::get(), File::get(), Http::get(), Cache::get() internally call file_get_contents/fopen/json_decode inside vendor code and will never be flagged. The lever is your own service layer: funnel I/O through one class whose methods you do analyse.
  • Expect false positives around first-class callables and variadics. Support for strlen(...)-style callables landed in v1.4.1 and is decent, but dynamically constructed callables, call_user_func with string function names, and variadic spreads still produce noise. Budget a named owner for triage — one round of "just mute the rule globally" and the whole investment evaporates.
  • First-class callables and reflection caching. v1.4.3 made reflection processing lazy; if you upgraded and behaviour changed, clear the cache first: vendor/bin/phpstan clear-result-cache. Stale caches after config or dependency changes are the usual cause of "it worked on my machine".
  • Baselines rot fast. Regenerating with --generate-baseline silently absorbs new violations into the ignore file, defeating the gate. Keep reportUnmatchedIgnoredErrors: true, prune entries when you fix them, and review baseline diffs in code review as if they were source.
  • Safe\ functions are plain global-namespace functions — they are not autoloaded classes, so no facades, no DI, no mocking in tests. In unit tests that expect the throwing behaviour, assert on the exception (Safe\Exceptions\FilesystemException, JsonException, PcreException), not on false returns you'd previously have asserted.
  • Error-handling strategy mismatch. If your team is moving toward Result/Either types or domain exceptions at boundaries, this rule pushes the opposite direction (throw from core calls). Decide once; mixing both conventions produces catch-blocks nobody understands.
  • Adoption risk is modest but real: ~63 stars, zero dependents, effectively a single-maintainer project. Pin the exact version, and keep in mind the entire rule is small enough that forking it in a day is a credible fallback — that bounds the downside. Don't make it load-bearing for anything you can't replace quickly.
  • Debugging a surprising finding: vendor/bin/phpstan analyse -v --debug shows which files the extension processed; --error-format=json gives you the identifier and message for scripting the burn-down.
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.
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
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