diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index f7049d84cd96..9d5fa632aec4 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -51,6 +51,13 @@ class Container implements ArrayAccess, ContainerContract */ protected $instances = []; + /** + * The container's scoped instances. + * + * @var array + */ + protected $scopedInstances = []; + /** * The registered type aliases. * @@ -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. * @@ -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. * @@ -1319,6 +1368,7 @@ public function flush() $this->bindings = []; $this->instances = []; $this->abstractAliases = []; + $this->scopedInstances = []; } /** diff --git a/src/Illuminate/Queue/QueueServiceProvider.php b/src/Illuminate/Queue/QueueServiceProvider.php index b87e55379579..e1a9a6fe4574 100755 --- a/src/Illuminate/Queue/QueueServiceProvider.php +++ b/src/Illuminate/Queue/QueueServiceProvider.php @@ -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 ); }); } diff --git a/src/Illuminate/Queue/Worker.php b/src/Illuminate/Queue/Worker.php index 0ed727eef373..37ba1b20ec31 100644 --- a/src/Illuminate/Queue/Worker.php +++ b/src/Illuminate/Queue/Worker.php @@ -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. * @@ -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; } /** @@ -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. diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php index 5cdb0204ddd9..7f37cb209b69 100755 --- a/tests/Container/ContainerTest.php +++ b/tests/Container/ContainerTest.php @@ -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; @@ -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);