Skip to content

Commit

Permalink
Finalize StateResetter, add more exceptions, add tests for 100% cov…
Browse files Browse the repository at this point in the history
…erage (#282)
  • Loading branch information
vjik committed Nov 30, 2021
1 parent f869690 commit 2a68d0f
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 5 deletions.
3 changes: 2 additions & 1 deletion composer.json
Expand Up @@ -34,7 +34,8 @@
"roave/infection-static-analysis-plugin": "^1.11",
"spatie/phpunit-watcher": "^1.23",
"vimeo/psalm": "^4.13",
"yiisoft/injector": "^1.0"
"yiisoft/injector": "^1.0",
"yiisoft/test-support": "^1.3"
},
"suggest": {
"yiisoft/injector": "^1.0@dev",
Expand Down
47 changes: 43 additions & 4 deletions src/StateResetter.php
Expand Up @@ -4,17 +4,25 @@

namespace Yiisoft\Di;

use Closure;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;

use function get_class;
use function gettype;
use function is_int;
use function is_object;

/**
* State resetter allows resetting state of the services that are currently stored in the container and have "reset"
* callback defined. The reset should be triggered after each request-response cycle in case you build long-running
* applications with tools like [Swoole](https://www.swoole.co.uk/) or [RoadRunner](https://roadrunner.dev/).
*/
class StateResetter
final class StateResetter
{
/**
* @var Closure[]|self[]
*/
private array $resetters = [];
private ContainerInterface $container;

Expand All @@ -41,20 +49,51 @@ public function reset(): void
}

/**
* @param array $resetters Array of reset callbacks. Each callback has access to the private and protected
* properties of the service instance, so you can set initial state of the service efficiently without creating
* a new instance.
* @param Closure[]|self[] $resetters Array of reset callbacks. Each callback has access to the private and
* protected properties of the service instance, so you can set initial state of the service efficiently
* without creating a new instance.
*/
public function setResetters(array $resetters): void
{
$this->resetters = [];
foreach ($resetters as $serviceId => $callback) {
if (is_int($serviceId)) {
if (!$callback instanceof self) {
throw new InvalidArgumentException(sprintf(
'State resetter object should be instance of "%s", "%s" given.',
self::class,
$this->getType($callback)
));
}
$this->resetters[] = $callback;
continue;
}

if (!$callback instanceof Closure) {
throw new InvalidArgumentException(
'Callback for state resetter should be closure in format ' .
'`function (ContainerInterface $container): void`. ' .
'Got "' . $this->getType($callback) . '".'
);
}

/** @var mixed $instance */
$instance = $this->container->get($serviceId);
if (!is_object($instance)) {
throw new InvalidArgumentException(
'State resetter supports resetting objects only. Container returned ' . gettype($instance) . '.'
);
}

$this->resetters[] = $callback->bindTo($instance, get_class($instance));
}
}

/**
* @param mixed $variable
*/
private function getType($variable): string
{
return is_object($variable) ? get_class($variable) : gettype($variable);
}
}
32 changes: 32 additions & 0 deletions tests/Unit/ContainerTest.php
Expand Up @@ -1151,6 +1151,38 @@ public function testResetter(): void
$this->assertSame(42, $engine->getNumber());
}

public function testResetterInDelegates(): void
{
$config = ContainerConfig::create()
->withDelegates([
static function (ContainerInterface $container) {
$config = ContainerConfig::create()
->withDefinitions([
EngineInterface::class => [
'class' => EngineMarkOne::class,
'setNumber()' => [42],
'reset' => function () {
$this->number = 42;
},
],
]);
return new Container($config);
},
]);
$container = new Container($config);

$engine = $container->get(EngineInterface::class);
$this->assertSame(42, $container->get(EngineInterface::class)->getNumber());

$engine->setNumber(45);
$this->assertSame(45, $container->get(EngineInterface::class)->getNumber());

$container->get(StateResetter::class)->reset();

$this->assertSame($engine, $container->get(EngineInterface::class));
$this->assertSame(42, $engine->getNumber());
}

public function testWrongResetter(): void
{
$this->expectException(TypeError::class);
Expand Down
61 changes: 61 additions & 0 deletions tests/Unit/StateResetterTest.php
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Unit;

use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use stdClass;
use Yiisoft\Di\StateResetter;
use Yiisoft\Di\Tests\Support\Car;
use Yiisoft\Test\Support\Container\SimpleContainer;

final class StateResetterTest extends TestCase
{
public function testNonStateResetterObject(): void
{
$resetter = new StateResetter(new SimpleContainer());

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
'State resetter object should be instance of "' . StateResetter::class . '", "stdClass" given.'
);
$resetter->setResetters([
new stdClass(),
]);
}

public function testStateResetterObjectForService(): void
{
$resetter = new StateResetter(new SimpleContainer());

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
'Callback for state resetter should be closure in format ' .
'`function (ContainerInterface $container): void`. ' .
'Got "' . StateResetter::class . '".'
);
$resetter->setResetters([
Car::class => $resetter,
]);
}

public function testResetNonObject(): void
{
$resetter = new StateResetter(
new SimpleContainer([
'value' => 42,
])
);

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
'State resetter supports resetting objects only. Container returned integer.'
);
$resetter->setResetters([
'value' => static function () {
},
]);
}
}

0 comments on commit 2a68d0f

Please sign in to comment.