Skip to content

Commit

Permalink
Add Config Loader that may fetch values from XML using XPath
Browse files Browse the repository at this point in the history
  • Loading branch information
roxblnfk committed May 1, 2024
1 parent cc3c4be commit fe18ffe
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 0 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"vimeo/psalm": "^5.11"
},
"suggest": {
"ext-simplexml": "To load trap.xml",
"roxblnfk/unpoly": "If you want to remove unnecessary PHP polyfills depend on PHP version."
}
}
12 changes: 12 additions & 0 deletions src/Service/Config/ConfigAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Service\Config;

/**
* @internal
*/
interface ConfigAttribute
{
}
87 changes: 87 additions & 0 deletions src/Service/Config/ConfigLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Service\Config;

/**
* @internal
*/
final class ConfigLoader
{
private \SimpleXMLElement|null $xml = null;

/**
* @param null|callable(): non-empty-string $xmlProvider
*/
public function __construct(
?callable $xmlProvider = null,
)
{
// Check SimpleXML extension
if (!\extension_loaded('simplexml')) {
return;
}

try {
$xml = $xmlProvider === null
? \file_get_contents(\dirname(__DIR__, 2) . '/trap.xml')
: $xmlProvider();
} catch (\Throwable) {
return;
}

$this->xml = \is_string($xml) ? \simplexml_load_string($xml) : null;
}

public function hidrate(object $config): void
{
// Read class properties
$reflection = new \ReflectionObject($config);
foreach ($reflection->getProperties() as $property) {
$attributes = $property->getAttributes(ConfigAttribute::class, \ReflectionAttribute::IS_INSTANCEOF);
if (\count($attributes) === 0) {
continue;
}

$this->injectValue($config, $property, $attributes);

Check failure on line 47 in src/Service/Config/ConfigLoader.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.2, OS ubuntu-latest)

InvalidArgument

src/Service/Config/ConfigLoader.php:47:52: InvalidArgument: Argument 3 of Buggregator\Trap\Service\Config\ConfigLoader::injectValue expects array<array-key, ReflectionAttribute<object>>, but non-empty-list<ReflectionAttribute<Buggregator\Trap\Service\Config\ConfigAttribute>> provided (see https://psalm.dev/004)
}
}

/**
* @param \ReflectionProperty $property
* @param array<\ReflectionAttribute> $attributes
*/
private function injectValue(object $config, \ReflectionProperty $property, array $attributes): void
{
foreach ($attributes as $attribute) {
$attribute = $attribute->newInstance();

$value = match (true) {
$attribute instanceof XPath => $this->xml?->xpath($attribute->path),
default => null,
};

if ($value === null) {
continue;
}

// Cast value to the property type
$type = $property->getType();
$result = match (true) {
$type === null => $value[0],
$type->allowsNull() && $value[0] === '' => null,

Check failure on line 73 in src/Service/Config/ConfigLoader.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.2, OS ubuntu-latest)

TypeDoesNotContainType

src/Service/Config/ConfigLoader.php:73:17: TypeDoesNotContainType: Type SimpleXMLElement for $value[0] is never =string() (see https://psalm.dev/056)

Check failure on line 73 in src/Service/Config/ConfigLoader.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.2, OS ubuntu-latest)

TypeDoesNotContainType

src/Service/Config/ConfigLoader.php:73:40: TypeDoesNotContainType: '' cannot be identical to SimpleXMLElement (see https://psalm.dev/056)
$type->isBuiltin() => match ($type->getName()) {

Check failure on line 74 in src/Service/Config/ConfigLoader.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.2, OS ubuntu-latest)

UndefinedMethod

src/Service/Config/ConfigLoader.php:74:24: UndefinedMethod: Method ReflectionType::isBuiltin does not exist (see https://psalm.dev/022)

Check failure on line 74 in src/Service/Config/ConfigLoader.php

View workflow job for this annotation

GitHub Actions / Psalm Validation (PHP 8.2, OS ubuntu-latest)

UndefinedMethod

src/Service/Config/ConfigLoader.php:74:53: UndefinedMethod: Method ReflectionType::getName does not exist (see https://psalm.dev/022)
'int' => (int) $value[0],
'float' => (float) $value[0],
'bool' => \filter_var($value[0], FILTER_VALIDATE_BOOLEAN),
default => $value[0],
},
default => $value[0],
};

// Set the property value
$property->setValue($config, $result);
}
}
}
17 changes: 17 additions & 0 deletions src/Service/Config/XPath.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Service\Config;

/**
* @internal
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
final class XPath implements ConfigAttribute
{
public function __construct(
public string $path,
) {
}
}
44 changes: 44 additions & 0 deletions tests/Unit/Service/Config/ConfigLoaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Tests\Unit\Service\Config;

use Buggregator\Trap\Service\Config\ConfigLoader;
use Buggregator\Trap\Service\Config\XPath;
use PHPUnit\Framework\TestCase;

final class ConfigLoaderTest extends TestCase
{
public function testSimpleHydration(): void
{
$dto = new class() {
#[XPath('/trap/container/@myBool')]
public bool $myBool;
#[XPath('/trap/container/MyInt/@value')]
public int $myInt;
#[XPath('/trap/@my-string')]
public string $myString;
#[XPath('/trap/container/MyFloat/@value')]
public float $myFloat;
};

$xml = <<<'XML'
<?xml version="1.0"?>
<trap my-string="foo-bar">
<container myBool="true">
<MyInt value="200"/>
<MyFloat value="42"/>
</container>
</trap>
XML;

$loader = new ConfigLoader(fn() => $xml);
$loader->hidrate($dto);

self::assertTrue($dto->myBool);
self::assertSame(200, $dto->myInt);
self::assertSame('foo-bar', $dto->myString);
self::assertSame(42.0, $dto->myFloat);
}
}
6 changes: 6 additions & 0 deletions trap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0"?>
<trap>
<frontend port="8000">
<EventsBuffer maxSize="200"/>
</frontend>
</trap>

0 comments on commit fe18ffe

Please sign in to comment.