Skip to content

[Bug]: Controller's injected constructor args not being flushed #1582

@steveneaston

Description

@steveneaston

What Happened

Classes injected into a controller's constructor that get modified as part of the request are not flushed when navigating to subsequent pages (either with navigate, click or refresh).

In our specific case, on the landing page, an API request is made which applies a criteria to a DB repository injected into the controller's constructor. The next page makes an API request to the same endpoint without the filter, however the criteria is still being applied to the DB query.

Injecting the repository class into the controller method works as expected. The issue only occurs when it's injected in the constructor.

How to Reproduce

The below is a simplified example for demonstration. There are two endpoints, one interacts with the constructor-injected class, the other with the method-injected class.

The first request to each endpoint sets a property on the injected class, the subsequent request doesn't and so expects the default value.

class Foo
{
    public string $bar = 'default';
}
class FooController
{
    public function __construct(protected Foo $foo)
    {
    }

    /**
     * Use Foo from controller's constructor
     */
    public function constructed(): string
    {
        if (request()->query('bar')) {
            $this->foo->bar = request()->query('bar');
        }
        return $this->foo->bar;
    }

    /**
     * Use Foo from method
     */
    public function isolated(Foo $foo): string
    {
        if (request()->query('bar')) {
            $foo->bar = request()->query('bar');
        }
        return $foo->bar;
    }
}
test('it flushes scope', function () {
    Route::get('constructed', [FooController::class, 'constructed']);
    Route::get('isolated', [FooController::class, 'isolated']);

    // Visit isolated method, set bar
    visit(url('isolated') . '?bar=qux')
        ->assertSee('qux')
        // Navigate back to isolated method, see bar is reset
        ->navigate(url('isolated'))
        ->assertSee('default')

        // Navigate to constructor method, set bar
        ->navigate(url('constructed') . '?bar=baz')
        ->assertSee('baz')

        // Navigate back to constructor, see bar is reset
        ->navigate(url('constructed'))
        ->assertSee('default'); // <== ❌ Should see "default" but gets "baz";
});

Sample Repository

No response

Pest Version

4.1.4

PHP Version

8.4.15

Operation System

Linux

Notes

Laravel 12.41.1

Adding the Scoped attribute to either Foo and/or FooController has no effect. I understand Octane runs in memory and so has guidance on what not to inject into constructors that get loaded on boot. Pest's browser plugin may require similar principles but this is a gotcha for us since we would expect the controller to be fresh on each request (we're not running on Octane).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions