Skip to content

[8.x] Introduce scoped instances #37521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/Illuminate/Container/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ class Container implements ArrayAccess, ContainerContract
*/
protected $instances = [];

/**
* The container's scoped instances.
*
* @var array
*/
protected $scopedInstances = [];

/**
* The registered type aliases.
*
Expand Down Expand Up @@ -393,6 +400,36 @@ public function singletonIf($abstract, $concrete = null)
}
}

/**
* Register a scoped binding in the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function scoped($abstract, $concrete = null)
{
$this->scopedInstances[] = $abstract;

$this->singleton($abstract, $concrete);
}

/**
* Register a scoped binding if it hasn't already been registered.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function scopedIf($abstract, $concrete = null)
{
if (! $this->bound($abstract)) {
$this->scopedInstances[] = $abstract;

$this->singleton($abstract, $concrete);
}
}

/**
* "Extend" an abstract type in the container.
*
Expand Down Expand Up @@ -1307,6 +1344,18 @@ public function forgetInstances()
$this->instances = [];
}

/**
* Clear all of the scoped instances from the container.
*
* @return void
*/
public function resetScope()
{
foreach ($this->scopedInstances as $scoped) {
unset($this->instances[$scoped]);
}
}

/**
* Flush the container of all bindings and resolved instances.
*
Expand All @@ -1319,6 +1368,7 @@ public function flush()
$this->bindings = [];
$this->instances = [];
$this->abstractAliases = [];
$this->scopedInstances = [];
}

/**
Expand Down
7 changes: 6 additions & 1 deletion src/Illuminate/Queue/QueueServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,16 @@ protected function registerWorker()
return $this->app->isDownForMaintenance();
};

$scopeResetter = function () use ($app) {
return $app->resetScope();
};

return new Worker(
$app['queue'],
$app['events'],
$app[ExceptionHandler::class],
$isDownForMaintenance
$isDownForMaintenance,
$scopeResetter
);
});
}
Expand Down
16 changes: 15 additions & 1 deletion src/Illuminate/Queue/Worker.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ class Worker
*/
protected $isDownForMaintenance;

/**
* The callback used to reset the application scope.
*
* @var callable
*/
protected $scopeResetter;

/**
* Indicates if the worker should exit.
*
Expand Down Expand Up @@ -93,17 +100,20 @@ class Worker
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @param \Illuminate\Contracts\Debug\ExceptionHandler $exceptions
* @param callable $isDownForMaintenance
* @param callable|null $scopeResetter
* @return void
*/
public function __construct(QueueManager $manager,
Dispatcher $events,
ExceptionHandler $exceptions,
callable $isDownForMaintenance)
callable $isDownForMaintenance,
callable $scopeResetter = null)
{
$this->events = $events;
$this->manager = $manager;
$this->exceptions = $exceptions;
$this->isDownForMaintenance = $isDownForMaintenance;
$this->scopeResetter = $scopeResetter;
}

/**
Expand Down Expand Up @@ -138,6 +148,10 @@ public function daemon($connectionName, $queue, WorkerOptions $options)
continue;
}

if (isset($this->scopeResetter)) {
($this->scopeResetter)();
}

// First, we will attempt to get the next job off of the queue. We will also
// register the timeout handler and reset the alarm for this job so it is
// not stuck in a frozen state forever. Then, we can fire off this job.
Expand Down
39 changes: 39 additions & 0 deletions tests/Container/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,31 @@ public function testSharedClosureResolution()
$this->assertSame($firstInstantiation, $secondInstantiation);
}

public function testScopedClosureResolution()
{
$container = new Container;
$container->scoped('class', function () {
return new stdClass;
});
$firstInstantiation = $container->make('class');
$secondInstantiation = $container->make('class');
$this->assertSame($firstInstantiation, $secondInstantiation);
}

public function testScopedClosureResets()
{
$container = new Container;
$container->scoped('class', function () {
return new stdClass;
});
$firstInstantiation = $container->make('class');

$container->resetScope();

$secondInstantiation = $container->make('class');
$this->assertNotSame($firstInstantiation, $secondInstantiation);
}

public function testAutoConcreteResolution()
{
$container = new Container;
Expand All @@ -122,6 +147,20 @@ public function testSharedConcreteResolution()
$this->assertSame($var1, $var2);
}

public function testScopedConcreteResolutionResets()
{
$container = new Container;
$container->scoped(ContainerConcreteStub::class);

$var1 = $container->make(ContainerConcreteStub::class);

$container->resetScope();

$var2 = $container->make(ContainerConcreteStub::class);

$this->assertNotSame($var1, $var2);
}

public function testBindFailsLoudlyWithInvalidArgument()
{
$this->expectException(TypeError::class);
Expand Down