diff --git a/src/Bootstrap.php b/src/Bootstrap.php
new file mode 100644
index 00000000..079bc7d8
--- /dev/null
+++ b/src/Bootstrap.php
@@ -0,0 +1,75 @@
+container;
+ unset($this->container);
+
+ return $c;
+ }
+
+ /**
+ * @param non-empty-string|null $xml File or XML content
+ */
+ public function withConfig(
+ ?string $xml = null,
+ array $inputOptions = [],
+ array $inputArguments = [],
+ array $environment = [],
+ ): self {
+ $args = [
+ 'env' => $environment,
+ 'inputArguments' => $inputArguments,
+ 'inputOptions' => $inputOptions,
+ ];
+
+ // XML config file
+ $xml === null or $args['xml'] = $this->readXml($xml);
+
+ // Register bindings
+ $this->container->bind(ConfigLoader::class, $args);
+
+ return $this;
+ }
+
+ private function readXml(string $fileOrContent): string
+ {
+ // Load content
+ if (\str_starts_with($fileOrContent, 'createRegistry($output);
- $container = new Container();
+ $container = Bootstrap::init()->withConfig(
+ xml: \dirname(__DIR__, 2) . '/trap.xml',
+ inputOptions: $input->getOptions(),
+ inputArguments: $input->getArguments(),
+ environment: \getenv(),
+ )->finish();
$container->set($registry);
$container->set($input, InputInterface::class);
$container->set(new Logger($output));
diff --git a/src/Config/Server/Frontend.php b/src/Config/Server/Frontend.php
index 76ad55bb..8ad3bb60 100644
--- a/src/Config/Server/Frontend.php
+++ b/src/Config/Server/Frontend.php
@@ -4,7 +4,7 @@
namespace Buggregator\Trap\Config\Server;
-use Buggregator\Trap\Service\Config\CliOption;
+use Buggregator\Trap\Service\Config\InputOption;
use Buggregator\Trap\Service\Config\Env;
use Buggregator\Trap\Service\Config\XPath;
@@ -15,7 +15,7 @@ final class Frontend
{
/** @var int<1, 65535> */
#[Env('TRAP_FRONTEND_PORT')]
- #[CliOption('ui')]
+ #[InputOption('ui')]
#[XPath('/trap/frontend/@port')]
public int $port = 8000;
}
diff --git a/src/Service/Config/ConfigLoader.php b/src/Service/Config/ConfigLoader.php
index 5b6b1196..625c6bc5 100644
--- a/src/Service/Config/ConfigLoader.php
+++ b/src/Service/Config/ConfigLoader.php
@@ -5,7 +5,6 @@
namespace Buggregator\Trap\Service\Config;
use Buggregator\Trap\Logger;
-use Symfony\Component\Console\Input\InputInterface;
/**
* @internal
@@ -15,31 +14,23 @@ final class ConfigLoader
private \SimpleXMLElement|null $xml = null;
/**
- * @param null|callable(): non-empty-string $xmlProvider
* @psalm-suppress RiskyTruthyFalsyComparison
*/
public function __construct(
- private Logger $logger,
- private ?InputInterface $cliInput,
- ?callable $xmlProvider = null,
- )
- {
- // Check SimpleXML extension
- if (!\extension_loaded('simplexml')) {
- return;
- }
-
- try {
- $xml = $xmlProvider === null
- ? \file_get_contents(\dirname(__DIR__, 3) . '/trap.xml')
- : $xmlProvider();
- } catch (\Throwable) {
- return;
+ private readonly Logger $logger,
+ private readonly array $env = [],
+ private readonly array $inputArguments = [],
+ private readonly array $inputOptions = [],
+ ?string $xml = null,
+ ) {
+ if (\is_string($xml)) {
+ // Check SimpleXML extension
+ if (!\extension_loaded('simplexml')) {
+ $logger->info('SimpleXML extension is not loaded.');
+ } else {
+ $this->xml = \simplexml_load_string($xml, options: \LIBXML_NOERROR) ?: null;
+ }
}
-
- $this->xml = \is_string($xml)
- ? (\simplexml_load_string($xml, options: \LIBXML_NOERROR) ?: null)
- : null;
}
public function hidrate(object $config): void
@@ -69,8 +60,9 @@ private function injectValue(object $config, \ReflectionProperty $property, arra
/** @var mixed $value */
$value = match (true) {
$attribute instanceof XPath => $this->xml?->xpath($attribute->path)[$attribute->key],
- $attribute instanceof Env => \getenv($attribute->name) === false ? null : \getenv($attribute->name),
- $attribute instanceof CliOption => $this->cliInput?->getOption($attribute->name),
+ $attribute instanceof Env => $this->env[$attribute->name] ?? null,
+ $attribute instanceof InputOption => $this->inputOptions[$attribute->name] ?? null,
+ $attribute instanceof InputArgument => $this->inputArguments[$attribute->name] ?? null,
default => null,
};
diff --git a/src/Service/Config/InputArgument.php b/src/Service/Config/InputArgument.php
new file mode 100644
index 00000000..ff863541
--- /dev/null
+++ b/src/Service/Config/InputArgument.php
@@ -0,0 +1,17 @@
+factory[$class] ?? null;
- $result = match(true) {
- $binding === null => $this->injector->make($class, $arguments),
- \is_array($binding) => $this->injector->make($class, \array_merge($binding, $arguments)),
- default => ($this->factory[$class])($this),
- };
+ if ($binding instanceof \Closure) {
+ $result = $binding($this);
+ } else {
+ try {
+ $result = $this->injector->make($class, \array_merge((array) $binding, $arguments));
+ } catch (\Throwable $e) {
+ throw new class(previous: $e) extends \RuntimeException implements NotFoundExceptionInterface {};
+ }
+ }
\assert($result instanceof $class, "Created object must be instance of {$class}.");
// Detect Trap related types
-
// Configs
if (\str_starts_with($class, 'Buggregator\\Trap\\Config\\')) {
// Hydrate config
diff --git a/tests/Unit/Service/Config/ConfigLoaderTest.php b/tests/Unit/Service/Config/ConfigLoaderTest.php
index b33ed0e4..781ceb07 100644
--- a/tests/Unit/Service/Config/ConfigLoaderTest.php
+++ b/tests/Unit/Service/Config/ConfigLoaderTest.php
@@ -4,8 +4,11 @@
namespace Buggregator\Trap\Tests\Unit\Service\Config;
-use Buggregator\Trap\Logger;
+use Buggregator\Trap\Bootstrap;
use Buggregator\Trap\Service\Config\ConfigLoader;
+use Buggregator\Trap\Service\Config\Env;
+use Buggregator\Trap\Service\Config\InputArgument;
+use Buggregator\Trap\Service\Config\InputOption;
use Buggregator\Trap\Service\Config\XPath;
use PHPUnit\Framework\TestCase;
@@ -22,8 +25,9 @@ public function testSimpleHydration(): void
public string $myString;
#[XPath('/trap/container/MyFloat/@value')]
public float $myFloat;
+ #[XPath('/trap/container/Nothing/@value')]
+ public float $none = 3.14;
};
-
$xml = <<<'XML'
@@ -34,12 +38,58 @@ public function testSimpleHydration(): void
XML;
- $loader = new ConfigLoader(new Logger(), null, fn() => $xml);
- $loader->hidrate($dto);
+ $this->createConfigLoader(xml: $xml)->hidrate($dto);
self::assertTrue($dto->myBool);
self::assertSame(200, $dto->myInt);
self::assertSame('foo-bar', $dto->myString);
self::assertSame(42.0, $dto->myFloat);
+ self::assertSame(3.14, $dto->none);
+ }
+
+ public function testAttributesOrder(): void
+ {
+ $dto = new class() {
+ #[XPath('/test/@foo')]
+ #[InputArgument('test')]
+ #[InputOption('test')]
+ #[Env('test')]
+ public int $int1;
+ #[Env('test')]
+ #[InputArgument('test')]
+ #[XPath('/test/@foo')]
+ #[InputOption('test')]
+ public int $int2;
+ #[InputArgument('test')]
+ #[Env('test')]
+ #[XPath('/test/@foo')]
+ #[InputOption('test')]
+ public int $int3;
+ };
+ $xml = <<<'XML'
+
+
+
+ XML;
+
+ $this
+ ->createConfigLoader(xml: $xml, opts: ['test' => 13], args: ['test' => 69], env: ['test' => 0])
+ ->hidrate($dto);
+
+ self::assertSame(42, $dto->int1);
+ self::assertSame(0, $dto->int2);
+ self::assertSame(69, $dto->int3);
+ }
+
+ private function createConfigLoader(
+ ?string $xml = null,
+ array $opts = [],
+ array $args = [],
+ array $env = [],
+ ): ConfigLoader {
+ return Bootstrap::init()
+ ->withConfig($xml, $opts, $args, $env)
+ ->finish()
+ ->get(ConfigLoader::class);
}
}