Add basic container implementation

This commit is contained in:
johannes.pichler
2020-06-03 22:08:25 +02:00
parent b0680b7e2c
commit 84c9078bb7
4 changed files with 167 additions and 2 deletions

View File

@ -82,6 +82,9 @@
"providers": [ "providers": [
"Pest\\Laravel\\PestServiceProvider" "Pest\\Laravel\\PestServiceProvider"
] ]
},
"branch-alias": {
"dev-master": "dev-feature/add-container"
} }
} }
} }

82
src/Support/Container.php Normal file
View File

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Pest\Support;
use Pest\Exceptions\ShouldNotHappen;
use ReflectionClass;
use ReflectionParameter;
/**
* @internal
*/
final class Container
{
/**
* @var array<string, mixed>
*/
private $instances = [];
/**
* Gets a dependency from the container.
*
* @return object
*/
public function get(string $id)
{
if (array_key_exists($id, $this->instances)) {
return $this->instances[$id];
}
$this->instances[$id] = $this->build($id);
return $this->instances[$id];
}
/**
* Adds the given instance to the container.
*
* @param mixed $instance
*/
public function add(string $id, $instance): void
{
$this->instances[$id] = $instance;
}
/**
* Tries to build the given instance.
*/
private function build(string $id): object
{
/** @phpstan-ignore-next-line */
$reflectionClass = new ReflectionClass($id);
if ($reflectionClass->isInstantiable()) {
$constructor = $reflectionClass->getConstructor();
if ($constructor !== null) {
$params = array_map(
function (ReflectionParameter $param) use ($id) {
$candidate = null;
if ($param->getType() !== null && $param->getType()->isBuiltin()) {
$candidate = $param->getName();
} elseif ($param->getClass() !== null) {
$candidate = $param->getClass()->getName();
} else {
throw ShouldNotHappen::fromMessage(sprintf('The type of `$%s` in `%s` cannot be determined.', $id, $param->getName()));
}
return $this->get($candidate);
},
$constructor->getParameters()
);
return $reflectionClass->newInstanceArgs($params);
}
}
throw ShouldNotHappen::fromMessage(sprintf('A dependency with the name `%s` cannot be resolved.', $id));
}
}

View File

@ -114,6 +114,15 @@
PASS Tests\Unit\Support\Backtrace PASS Tests\Unit\Support\Backtrace
✓ it gets file name from called file ✓ it gets file name from called file
PASS Tests\Unit\Support\Container
✓ it exists
✓ it gets an instance
✓ it creates an instance and resolves parameters
✓ it creates an instance and resolves also sub parameters
✓ it can resolve builtin value types
✓ it cannot resolve a parameter that requires additional dependencies
✓ it cannot resolve a parameter without type
PASS Tests\Unit\Support\Reflection PASS Tests\Unit\Support\Reflection
✓ it gets file name from closure ✓ it gets file name from closure
✓ it gets property values ✓ it gets property values
@ -130,5 +139,5 @@
WARN Tests\Visual\Success WARN Tests\Visual\Success
s visual snapshot of test suite on success s visual snapshot of test suite on success
Tests: 6 skipped, 69 passed Tests: 6 skipped, 76 passed
Time: 2.34s Time: 3.32s

View File

@ -0,0 +1,71 @@
<?php
use Pest\Exceptions\ShouldNotHappen;
use Pest\Support\Container;
use Pest\TestSuite;
uses()->group('container');
beforeEach(function () {
$this->container = new Container();
});
it('exists')
->assertTrue(class_exists(Container::class));
it('gets an instance', function () {
$this->container->add(Container::class, $this->container);
assertSame($this->container, $this->container->get(Container::class));
});
it('creates an instance and resolves parameters', function () {
$this->container->add(Container::class, $this->container);
$instance = $this->container->get(ClassWithDependency::class);
assertInstanceOf(ClassWithDependency::class, $instance);
});
it('creates an instance and resolves also sub parameters', function () {
$this->container->add(Container::class, $this->container);
$instance = $this->container->get(ClassWithSubDependency::class);
assertInstanceOf(ClassWithSubDependency::class, $instance);
});
it('can resolve builtin value types', function () {
$this->container->add('rootPath', getcwd());
$instance = $this->container->get(TestSuite::class);
assertInstanceOf(TestSuite::class, $instance);
});
it('cannot resolve a parameter that requires additional dependencies', function () {
$this->expectException(ShouldNotHappen::class);
$this->container->get(ClassWithDependency::class);
});
it('cannot resolve a parameter without type', function () {
$this->expectException(ShouldNotHappen::class);
$this->container->get(ClassWithoutTypeParameter::class);
});
class ClassWithDependency
{
public function __construct(Container $container)
{
}
}
class ClassWithSubDependency
{
public function __construct(ClassWithDependency $param)
{
}
}
class ClassWithoutTypeParameter
{
public function __construct($param)
{
}
}