misc: introduce functions container to wrap definition handling

This commit is contained in:
Romain Canon 2022-03-17 20:48:29 +01:00
parent fdef93074c
commit fd11177b06
11 changed files with 257 additions and 126 deletions

View 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
);
}
}

View 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
);
}
}

View 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]),
];
}
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View 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()
);
}
}

View File

@ -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();
}
}

View 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);
}
}