mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 15:57:21 +01:00
Merge pull request #65 from fetzi/feature/add-container
Add basic container implementation
This commit is contained in:
9
bin/pest
9
bin/pest
@ -4,8 +4,10 @@
|
||||
use NunoMaduro\Collision\Provider;
|
||||
use Pest\Actions\ValidatesEnvironment;
|
||||
use Pest\Console\Command;
|
||||
use Pest\Support\Container;
|
||||
use Pest\TestSuite;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
(static function () {
|
||||
// Used when Pest is required using composer.
|
||||
@ -25,8 +27,13 @@ use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
$rootPath = getcwd();
|
||||
|
||||
$testSuite = TestSuite::getInstance($rootPath);
|
||||
$output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, true);
|
||||
|
||||
$container = Container::getInstance();
|
||||
$container->add(TestSuite::class, $testSuite);
|
||||
$container->add(OutputInterface::class, $output);
|
||||
|
||||
ValidatesEnvironment::in($testSuite);
|
||||
|
||||
exit((new Command($testSuite, new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, true)))->run($_SERVER['argv']));
|
||||
exit($container->get(Command::class)->run($_SERVER['argv']));
|
||||
})();
|
||||
|
||||
@ -85,6 +85,9 @@
|
||||
"providers": [
|
||||
"Pest\\Laravel\\PestServiceProvider"
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "dev-feature/add-container"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ final class Command extends BaseCommand
|
||||
|
||||
/** @var HandlesArguments $plugin */
|
||||
foreach ($plugins as $plugin) {
|
||||
$argv = $plugin->handleArguments($this->testSuite, $argv);
|
||||
$argv = $plugin->handleArguments($argv);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -134,7 +134,7 @@ final class Command extends BaseCommand
|
||||
|
||||
/** @var AddsOutput $plugin */
|
||||
foreach ($plugins as $plugin) {
|
||||
$result = $plugin->addOutput($this->testSuite, $this->output, $result);
|
||||
$result = $plugin->addOutput($result);
|
||||
}
|
||||
|
||||
exit($result);
|
||||
|
||||
@ -4,9 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts\Plugins;
|
||||
|
||||
use Pest\TestSuite;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -15,5 +12,5 @@ interface AddsOutput
|
||||
/**
|
||||
* Allows to add custom output after the test suite was executed.
|
||||
*/
|
||||
public function addOutput(TestSuite $testSuite, OutputInterface $output, int $testReturnCode): int;
|
||||
public function addOutput(int $testReturnCode): int;
|
||||
}
|
||||
|
||||
@ -4,8 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Contracts\Plugins;
|
||||
|
||||
use Pest\TestSuite;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -21,5 +19,5 @@ interface HandlesArguments
|
||||
*
|
||||
* @return array<int, string> the updated list of arguments
|
||||
*/
|
||||
public function handleArguments(TestSuite $testSuite, array $arguments): array;
|
||||
public function handleArguments(array $arguments): array;
|
||||
}
|
||||
|
||||
99
src/Support/Container.php
Normal file
99
src/Support/Container.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Support;
|
||||
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
use ReflectionClass;
|
||||
use ReflectionParameter;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Container
|
||||
{
|
||||
/**
|
||||
* @var self
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private $instances = [];
|
||||
|
||||
/**
|
||||
* Gets a new or already existing container.
|
||||
*/
|
||||
public static function getInstance(): self
|
||||
{
|
||||
if (static::$instance === null) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
@ -118,6 +118,15 @@
|
||||
PASS Tests\Unit\Support\Backtrace
|
||||
✓ 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
|
||||
✓ it gets file name from closure
|
||||
✓ it gets property values
|
||||
@ -134,5 +143,5 @@
|
||||
WARN Tests\Visual\Success
|
||||
s visual snapshot of test suite on success
|
||||
|
||||
Tests: 6 skipped, 71 passed
|
||||
Time: 2.87s
|
||||
Tests: 6 skipped, 78 passed
|
||||
Time: 3.40s
|
||||
|
||||
71
tests/Unit/Support/Container.php
Normal file
71
tests/Unit/Support/Container.php
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user