mirror of
https://github.com/danog/Valinor.git
synced 2024-11-30 04:39:05 +01:00
misc: introduce functions container to wrap definition handling
This commit is contained in:
parent
fdef93074c
commit
fd11177b06
20
src/Definition/Exception/CallbackNotFound.php
Normal file
20
src/Definition/Exception/CallbackNotFound.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition\Exception;
|
||||
|
||||
use CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class CallbackNotFound extends RuntimeException
|
||||
{
|
||||
public function __construct(FunctionDefinition $function)
|
||||
{
|
||||
parent::__construct(
|
||||
"The callback associated to `{$function->signature()}` could not be found.",
|
||||
1647523495
|
||||
);
|
||||
}
|
||||
}
|
22
src/Definition/Exception/FunctionNotFound.php
Normal file
22
src/Definition/Exception/FunctionNotFound.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/** @internal */
|
||||
final class FunctionNotFound extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* @param string|int $key
|
||||
*/
|
||||
public function __construct($key)
|
||||
{
|
||||
parent::__construct(
|
||||
"The function `$key` was not found.",
|
||||
1647523444
|
||||
);
|
||||
}
|
||||
}
|
89
src/Definition/FunctionsContainer.php
Normal file
89
src/Definition/FunctionsContainer.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition;
|
||||
|
||||
use CuyZ\Valinor\Definition\Exception\CallbackNotFound;
|
||||
use CuyZ\Valinor\Definition\Exception\FunctionNotFound;
|
||||
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
use function array_keys;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @implements IteratorAggregate<FunctionDefinition>
|
||||
*/
|
||||
final class FunctionsContainer implements IteratorAggregate
|
||||
{
|
||||
private FunctionDefinitionRepository $functionDefinitionRepository;
|
||||
|
||||
/** @var array<callable> */
|
||||
private array $callables;
|
||||
|
||||
/** @var array<array{definition: FunctionDefinition, callback: callable}> */
|
||||
private array $functions = [];
|
||||
|
||||
/**
|
||||
* @param array<callable> $callables
|
||||
*/
|
||||
public function __construct(FunctionDefinitionRepository $functionDefinitionRepository, array $callables)
|
||||
{
|
||||
$this->functionDefinitionRepository = $functionDefinitionRepository;
|
||||
$this->callables = $callables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $key
|
||||
*/
|
||||
public function has($key): bool
|
||||
{
|
||||
return isset($this->callables[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $key
|
||||
*/
|
||||
public function get($key): FunctionDefinition
|
||||
{
|
||||
if (! $this->has($key)) {
|
||||
throw new FunctionNotFound($key);
|
||||
}
|
||||
|
||||
return $this->function($key)['definition'];
|
||||
}
|
||||
|
||||
public function callback(FunctionDefinition $function): callable
|
||||
{
|
||||
foreach ($this->functions as $data) {
|
||||
if ($function === $data['definition']) {
|
||||
return $data['callback'];
|
||||
}
|
||||
}
|
||||
|
||||
throw new CallbackNotFound($function);
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
foreach (array_keys($this->callables) as $key) {
|
||||
yield $key => $this->function($key)['definition'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $key
|
||||
* @return array{definition: FunctionDefinition, callback: callable}
|
||||
*/
|
||||
private function function($key): array
|
||||
{
|
||||
/** @infection-ignore-all */
|
||||
return $this->functions[$key] ??= [
|
||||
'callback' => $this->callables[$key],
|
||||
'definition' => $this->functionDefinitionRepository->for($this->callables[$key]),
|
||||
];
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ use CuyZ\Valinor\Cache\RuntimeCache;
|
||||
use CuyZ\Valinor\Cache\VersionedCache;
|
||||
use CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use CuyZ\Valinor\Definition\FunctionsContainer;
|
||||
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
|
||||
use CuyZ\Valinor\Definition\Repository\Cache\CacheClassDefinitionRepository;
|
||||
use CuyZ\Valinor\Definition\Repository\Cache\CacheFunctionDefinitionRepository;
|
||||
@ -124,17 +125,21 @@ final class Container
|
||||
|
||||
$builder = new InterfaceNodeBuilder(
|
||||
$builder,
|
||||
$this->get(FunctionDefinitionRepository::class),
|
||||
new FunctionsContainer(
|
||||
$this->get(FunctionDefinitionRepository::class),
|
||||
$settings->interfaceMapping
|
||||
),
|
||||
$this->get(TypeParser::class),
|
||||
$settings->interfaceMapping,
|
||||
);
|
||||
|
||||
$builder = new CasterProxyNodeBuilder($builder);
|
||||
$builder = new VisitorNodeBuilder($builder, $settings->nodeVisitors);
|
||||
$builder = new ValueAlteringNodeBuilder(
|
||||
$builder,
|
||||
$this->get(FunctionDefinitionRepository::class),
|
||||
$settings->valueModifier
|
||||
new FunctionsContainer(
|
||||
$this->get(FunctionDefinitionRepository::class),
|
||||
$settings->valueModifier
|
||||
)
|
||||
);
|
||||
$builder = new ShellVisitorNodeBuilder($builder, $this->get(ShellVisitor::class));
|
||||
|
||||
@ -150,9 +155,11 @@ final class Container
|
||||
|
||||
$factory = new ObjectBindingBuilderFactory(
|
||||
$factory,
|
||||
$this->get(FunctionDefinitionRepository::class),
|
||||
$this->get(ObjectBuilderFilterer::class),
|
||||
$settings->objectBinding,
|
||||
new FunctionsContainer(
|
||||
$this->get(FunctionDefinitionRepository::class),
|
||||
$settings->objectBinding
|
||||
)
|
||||
);
|
||||
|
||||
return new AttributeObjectBuilderFactory($factory);
|
||||
|
@ -5,6 +5,8 @@ declare(strict_types=1);
|
||||
namespace CuyZ\Valinor\Library;
|
||||
|
||||
use CuyZ\Valinor\Mapper\Tree\Node;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
@ -30,5 +32,6 @@ final class Settings
|
||||
public function __construct()
|
||||
{
|
||||
$this->cacheDir = sys_get_temp_dir();
|
||||
$this->interfaceMapping[DateTimeInterface::class] = static fn () => DateTimeImmutable::class;
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,7 @@ declare(strict_types=1);
|
||||
namespace CuyZ\Valinor\Mapper\Object\Factory;
|
||||
|
||||
use CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
|
||||
use CuyZ\Valinor\Definition\FunctionsContainer;
|
||||
use CuyZ\Valinor\Mapper\Object\CallbackObjectBuilder;
|
||||
use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
|
||||
use CuyZ\Valinor\Mapper\Object\ObjectBuilderFilterer;
|
||||
@ -16,38 +15,27 @@ final class ObjectBindingBuilderFactory implements ObjectBuilderFactory
|
||||
{
|
||||
private ObjectBuilderFactory $delegate;
|
||||
|
||||
private FunctionDefinitionRepository $functionDefinitionRepository;
|
||||
|
||||
private ObjectBuilderFilterer $objectBuilderFilterer;
|
||||
|
||||
/** @var list<callable> */
|
||||
private array $callbacks;
|
||||
private FunctionsContainer $functions;
|
||||
|
||||
/** @var list<FunctionDefinition> */
|
||||
private array $functions;
|
||||
|
||||
/**
|
||||
* @param list<callable> $callbacks
|
||||
*/
|
||||
public function __construct(
|
||||
ObjectBuilderFactory $delegate,
|
||||
FunctionDefinitionRepository $functionDefinitionRepository,
|
||||
ObjectBuilderFilterer $objectBuilderFilterer,
|
||||
array $callbacks
|
||||
FunctionsContainer $functions
|
||||
) {
|
||||
$this->delegate = $delegate;
|
||||
$this->functionDefinitionRepository = $functionDefinitionRepository;
|
||||
$this->objectBuilderFilterer = $objectBuilderFilterer;
|
||||
$this->callbacks = $callbacks;
|
||||
$this->functions = $functions;
|
||||
}
|
||||
|
||||
public function for(ClassDefinition $class, $source): ObjectBuilder
|
||||
{
|
||||
$builders = [];
|
||||
|
||||
foreach ($this->functions() as $key => $function) {
|
||||
foreach ($this->functions as $function) {
|
||||
if ($function->returnType()->matches($class->type())) {
|
||||
$builders[] = new CallbackObjectBuilder($function, $this->callbacks[$key]);
|
||||
$builders[] = new CallbackObjectBuilder($function, $this->functions->callback($function));
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,20 +45,4 @@ final class ObjectBindingBuilderFactory implements ObjectBuilderFactory
|
||||
|
||||
return $this->objectBuilderFilterer->filter($source, ...$builders);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FunctionDefinition[]
|
||||
*/
|
||||
private function functions(): array
|
||||
{
|
||||
if (! isset($this->functions)) {
|
||||
$this->functions = [];
|
||||
|
||||
foreach ($this->callbacks as $key => $callback) {
|
||||
$this->functions[$key] = $this->functionDefinitionRepository->for($callback);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->functions;
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ declare(strict_types=1);
|
||||
namespace CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use CuyZ\Valinor\Definition\FunctionsContainer;
|
||||
use CuyZ\Valinor\Definition\Parameters;
|
||||
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
|
||||
use CuyZ\Valinor\Mapper\Object\Exception\InvalidSourceForInterface;
|
||||
use CuyZ\Valinor\Mapper\Tree\Exception\InvalidInterfaceResolverReturnType;
|
||||
use CuyZ\Valinor\Mapper\Tree\Exception\InvalidTypeResolvedForInterface;
|
||||
@ -18,8 +18,6 @@ use CuyZ\Valinor\Type\Parser\TypeParser;
|
||||
use CuyZ\Valinor\Type\Resolver\Exception\CannotResolveTypeFromInterface;
|
||||
use CuyZ\Valinor\Type\Types\ClassType;
|
||||
use CuyZ\Valinor\Type\Types\InterfaceType;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Exception;
|
||||
|
||||
/** @internal */
|
||||
@ -27,31 +25,15 @@ final class InterfaceNodeBuilder implements NodeBuilder
|
||||
{
|
||||
private NodeBuilder $delegate;
|
||||
|
||||
private FunctionsContainer $functions;
|
||||
|
||||
private TypeParser $typeParser;
|
||||
|
||||
private FunctionDefinitionRepository $functionDefinitionRepository;
|
||||
|
||||
/** @var array<interface-string, callable> */
|
||||
private array $callbacks;
|
||||
|
||||
/** @var array<interface-string, FunctionDefinition> */
|
||||
private array $functions;
|
||||
|
||||
/**
|
||||
* @param array<interface-string, callable> $interfaceMapping
|
||||
*/
|
||||
public function __construct(
|
||||
NodeBuilder $delegate,
|
||||
FunctionDefinitionRepository $functionDefinitionRepository,
|
||||
TypeParser $typeParser,
|
||||
array $interfaceMapping
|
||||
) {
|
||||
public function __construct(NodeBuilder $delegate, FunctionsContainer $functions, TypeParser $typeParser)
|
||||
{
|
||||
$this->delegate = $delegate;
|
||||
$this->functionDefinitionRepository = $functionDefinitionRepository;
|
||||
$this->functions = $functions;
|
||||
$this->typeParser = $typeParser;
|
||||
|
||||
$this->callbacks = $interfaceMapping;
|
||||
$this->callbacks[DateTimeInterface::class] ??= static fn () => DateTimeImmutable::class;
|
||||
}
|
||||
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
|
||||
@ -64,14 +46,14 @@ final class InterfaceNodeBuilder implements NodeBuilder
|
||||
|
||||
$interfaceName = $type->className();
|
||||
|
||||
if (! isset($this->callbacks[$interfaceName])) {
|
||||
if (! $this->functions->has($interfaceName)) {
|
||||
throw new CannotResolveTypeFromInterface($interfaceName);
|
||||
}
|
||||
|
||||
/** @infection-ignore-all */
|
||||
$this->functions[$interfaceName] ??= $this->functionDefinitionRepository->for($this->callbacks[$interfaceName]);
|
||||
$function = $this->functions->get($interfaceName);
|
||||
$callback = $this->functions->callback($function);
|
||||
|
||||
$children = $this->children($shell, $this->functions[$interfaceName], $rootBuilder);
|
||||
$children = $this->children($shell, $function, $rootBuilder);
|
||||
$arguments = [];
|
||||
|
||||
foreach ($children as $child) {
|
||||
@ -83,7 +65,7 @@ final class InterfaceNodeBuilder implements NodeBuilder
|
||||
}
|
||||
|
||||
try {
|
||||
$signature = ($this->callbacks[$interfaceName])(...$arguments);
|
||||
$signature = $callback(...$arguments);
|
||||
} catch (Exception $exception) {
|
||||
throw ThrowableMessage::from($exception);
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Mapper\Tree\Builder;
|
||||
|
||||
use CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
|
||||
use CuyZ\Valinor\Definition\FunctionsContainer;
|
||||
use CuyZ\Valinor\Mapper\Tree\Node;
|
||||
use CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
|
||||
@ -14,25 +13,12 @@ final class ValueAlteringNodeBuilder implements NodeBuilder
|
||||
{
|
||||
private NodeBuilder $delegate;
|
||||
|
||||
private FunctionDefinitionRepository $functionDefinitionRepository;
|
||||
private FunctionsContainer $functions;
|
||||
|
||||
/** @var list<callable> */
|
||||
private array $callbacks;
|
||||
|
||||
/** @var list<FunctionDefinition> */
|
||||
private array $functions;
|
||||
|
||||
/**
|
||||
* @param list<callable> $callbacks
|
||||
*/
|
||||
public function __construct(
|
||||
NodeBuilder $delegate,
|
||||
FunctionDefinitionRepository $functionDefinitionRepository,
|
||||
array $callbacks
|
||||
) {
|
||||
public function __construct(NodeBuilder $delegate, FunctionsContainer $functions)
|
||||
{
|
||||
$this->delegate = $delegate;
|
||||
$this->functionDefinitionRepository = $functionDefinitionRepository;
|
||||
$this->callbacks = $callbacks;
|
||||
$this->functions = $functions;
|
||||
}
|
||||
|
||||
public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
|
||||
@ -46,7 +32,7 @@ final class ValueAlteringNodeBuilder implements NodeBuilder
|
||||
$value = $node->value();
|
||||
$type = $node->type();
|
||||
|
||||
foreach ($this->functions() as $key => $function) {
|
||||
foreach ($this->functions as $function) {
|
||||
$parameters = $function->parameters();
|
||||
|
||||
if (count($parameters) === 0) {
|
||||
@ -54,27 +40,11 @@ final class ValueAlteringNodeBuilder implements NodeBuilder
|
||||
}
|
||||
|
||||
if ($parameters->at(0)->type()->matches($type)) {
|
||||
$value = ($this->callbacks[$key])($value);
|
||||
$value = ($this->functions->callback($function))($value);
|
||||
$node = $node->withValue($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FunctionDefinition[]
|
||||
*/
|
||||
private function functions(): array
|
||||
{
|
||||
if (! isset($this->functions)) {
|
||||
$this->functions = [];
|
||||
|
||||
foreach ($this->callbacks as $key => $callback) {
|
||||
$this->functions[$key] = $this->functionDefinitionRepository->for($callback);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->functions;
|
||||
}
|
||||
}
|
||||
|
34
tests/Fake/Definition/FakeFunctionDefinition.php
Normal file
34
tests/Fake/Definition/FakeFunctionDefinition.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Fake\Definition;
|
||||
|
||||
use CuyZ\Valinor\Definition\EmptyAttributes;
|
||||
use CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use CuyZ\Valinor\Definition\ParameterDefinition;
|
||||
use CuyZ\Valinor\Definition\Parameters;
|
||||
use CuyZ\Valinor\Type\Types\NativeStringType;
|
||||
|
||||
final class FakeFunctionDefinition
|
||||
{
|
||||
public static function new(): FunctionDefinition
|
||||
{
|
||||
return new FunctionDefinition(
|
||||
'foo',
|
||||
'foo:42-1337',
|
||||
new Parameters(
|
||||
new ParameterDefinition(
|
||||
'bar',
|
||||
'foo::bar',
|
||||
NativeStringType::get(),
|
||||
false,
|
||||
false,
|
||||
'foo',
|
||||
EmptyAttributes::get()
|
||||
)
|
||||
),
|
||||
NativeStringType::get()
|
||||
);
|
||||
}
|
||||
}
|
@ -4,32 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Fake\Definition\Repository;
|
||||
|
||||
use CuyZ\Valinor\Definition\EmptyAttributes;
|
||||
use CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use CuyZ\Valinor\Definition\ParameterDefinition;
|
||||
use CuyZ\Valinor\Definition\Parameters;
|
||||
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
|
||||
use CuyZ\Valinor\Type\Types\NativeStringType;
|
||||
use CuyZ\Valinor\Tests\Fake\Definition\FakeFunctionDefinition;
|
||||
|
||||
final class FakeFunctionDefinitionRepository implements FunctionDefinitionRepository
|
||||
{
|
||||
public function for(callable $function): FunctionDefinition
|
||||
{
|
||||
return new FunctionDefinition(
|
||||
'foo',
|
||||
'foo:42-1337',
|
||||
new Parameters(
|
||||
new ParameterDefinition(
|
||||
'bar',
|
||||
'foo::bar',
|
||||
NativeStringType::get(),
|
||||
false,
|
||||
false,
|
||||
'foo',
|
||||
EmptyAttributes::get()
|
||||
)
|
||||
),
|
||||
NativeStringType::get()
|
||||
);
|
||||
return FakeFunctionDefinition::new();
|
||||
}
|
||||
}
|
||||
|
50
tests/Unit/Definition/FunctionsContainerTest.php
Normal file
50
tests/Unit/Definition/FunctionsContainerTest.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Definition;
|
||||
|
||||
use CuyZ\Valinor\Definition\Exception\CallbackNotFound;
|
||||
use CuyZ\Valinor\Definition\Exception\FunctionNotFound;
|
||||
use CuyZ\Valinor\Definition\FunctionsContainer;
|
||||
use CuyZ\Valinor\Tests\Fake\Definition\FakeFunctionDefinition;
|
||||
use CuyZ\Valinor\Tests\Fake\Definition\Repository\FakeFunctionDefinitionRepository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
use function iterator_to_array;
|
||||
|
||||
final class FunctionsContainerTest extends TestCase
|
||||
{
|
||||
public function test_get_unknown_function_throws_exception(): void
|
||||
{
|
||||
$this->expectException(FunctionNotFound::class);
|
||||
$this->expectExceptionCode(1647523444);
|
||||
$this->expectExceptionMessage('The function `unknown` was not found.');
|
||||
|
||||
(new FunctionsContainer(new FakeFunctionDefinitionRepository(), []))->get('unknown');
|
||||
}
|
||||
|
||||
public function test_get_unknown_callback_throws_exception(): void
|
||||
{
|
||||
$function = FakeFunctionDefinition::new();
|
||||
|
||||
$this->expectException(CallbackNotFound::class);
|
||||
$this->expectExceptionCode(1647523495);
|
||||
$this->expectExceptionMessage("The callback associated to `{$function->signature()}` could not be found.");
|
||||
|
||||
(new FunctionsContainer(new FakeFunctionDefinitionRepository(), []))->callback($function);
|
||||
}
|
||||
|
||||
public function test_keys_are_kept_when_iterating(): void
|
||||
{
|
||||
$functions = (new FunctionsContainer(new FakeFunctionDefinitionRepository(), [
|
||||
'foo' => fn () => 'foo',
|
||||
'bar' => fn () => 'bar',
|
||||
]));
|
||||
|
||||
$functions = iterator_to_array($functions);
|
||||
|
||||
self::assertArrayHasKey('foo', $functions);
|
||||
self::assertArrayHasKey('bar', $functions);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user