Weave Code
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

Plugin Laravel Laravel Package

psalm/plugin-laravel

Laravel Psalm plugin for deep static analysis plus taint-based security scanning. Detects SQL injection, XSS, SSRF, shell injection, path traversal, and open redirects by tracking user input through Laravel code without running it.

View on GitHub
Deep Wiki
Context7

title: OctaneIncompatibleBinding parent: Custom Issues nav_order: 7

OctaneIncompatibleBinding

Emitted when a singleton() or singletonIf() binding closure resolves a request-scoped Laravel service such as Request, Session, Auth, or Config.

Auto-enabled when laravel/octane is installed. Projects that don't depend on the package directly can opt in via <findOctaneIncompatibleBinding value="true" /> in psalm.xml. To opt out even when laravel/octane is installed, set <findOctaneIncompatibleBinding value="false" />. See Configuration.

bind(), bindIf(), scoped(), and scopedIf() are NOT flagged. bind() re-executes the closure on every resolution; scoped() instances are flushed between requests under Octane (via Container::forgetScopedInstances()), so neither leaks captured state.

Why this is a problem

Under traditional PHP-FPM, every request boots a fresh application instance, so even a "shared" binding is really re-created per request.

Under Laravel Octane, the application instance is reused across requests. A shared binding closure runs once and the result is kept for the rest of the worker's lifetime. If that closure captures request-scoped state (a Request, the current Auth user, a Session), every subsequent request sees stale state from the first resolution.

This is a documented Octane caveat:

You should avoid injecting the application container or HTTP request into the constructor of any object you register as a singleton.

Config is a special case. The repository binding itself is a singleton, but Octane resets its state between requests, so values read inside a singleton closure freeze at first-resolution time even though the repository instance is reused.

Examples

// Bad. The Request is captured once and reused for every future request.
$this->app->singleton(MyService::class, function ($app) {
    return new MyService($app->make(Request::class)); // OctaneIncompatibleBinding
});

// Good. Use bind() so the closure re-runs on every resolution.
$this->app->bind(MyService::class, function ($app) {
    return new MyService($app->make(Request::class));
});

// Also good. Keep the singleton, but resolve the request-scoped service at the
// point of use instead of constructor injection.
class MyService
{
    public function __construct(private \Illuminate\Contracts\Container\Container $container) {}

    public function handle(): void
    {
        $request = $this->container->make(Request::class);
        // ...
    }
}
// Bad. Config values read inside a singleton closure are frozen at first resolution.
$this->app->singleton(MyService::class, function ($app) {
    $config = $app->make('config'); // OctaneIncompatibleBinding
    return new MyService($config->get('myservice.endpoint'));
});

// Good. Read config via the facade at call site so the lookup happens on each resolution.
$this->app->singleton(MyService::class, function () {
    return new MyService(Config::string('myservice.endpoint'));
});

How to fix

  • Change singleton() to scoped(). Octane flushes scoped instances between requests, so the closure runs once per request instead of once per worker.
  • Or change singleton() to bind(). The closure will re-run on every resolution (simpler but no intra-request caching).
  • Or keep the singleton and move the request-scoped resolution out of the constructor: inject the container and resolve lazily inside the method that actually uses it.
  • For config specifically, replace $app->make('config') with the Config facade inside the closure.
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.
codraw/entity-migrator
codraw/doctrine-extra
codraw/aws-tool-kit
codraw/validator
codraw/workflow
codraw/open-api
codraw/cron-job
codraw/process
codraw/log
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony