mirror of
https://github.com/danog/Valinor.git
synced 2024-11-30 04:39:05 +01:00
fix: properly handle callable objects of the same class
Using two instances of the same class implementing the `__invoke()` method in one of the mapper builder methods will now be properly handled by the library
This commit is contained in:
parent
444747ab0a
commit
ae7ddcf3ca
@ -1,20 +0,0 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
30
src/Definition/FunctionObject.php
Normal file
30
src/Definition/FunctionObject.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Definition;
|
||||
|
||||
/** @internal */
|
||||
final class FunctionObject
|
||||
{
|
||||
private FunctionDefinition $definition;
|
||||
|
||||
/** @var callable */
|
||||
private $callback;
|
||||
|
||||
public function __construct(FunctionDefinition $definition, callable $callback)
|
||||
{
|
||||
$this->definition = $definition;
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
public function definition(): FunctionDefinition
|
||||
{
|
||||
return $this->definition;
|
||||
}
|
||||
|
||||
public function callback(): callable
|
||||
{
|
||||
return $this->callback;
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@ 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;
|
||||
@ -15,7 +13,7 @@ use function array_keys;
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @implements IteratorAggregate<string|int, FunctionDefinition>
|
||||
* @implements IteratorAggregate<string|int, FunctionObject>
|
||||
*/
|
||||
final class FunctionsContainer implements IteratorAggregate
|
||||
{
|
||||
@ -24,7 +22,7 @@ final class FunctionsContainer implements IteratorAggregate
|
||||
/** @var array<callable> */
|
||||
private array $callables;
|
||||
|
||||
/** @var array<array{definition: FunctionDefinition, callback: callable}> */
|
||||
/** @var array<FunctionObject> */
|
||||
private array $functions = [];
|
||||
|
||||
/**
|
||||
@ -47,43 +45,26 @@ final class FunctionsContainer implements IteratorAggregate
|
||||
/**
|
||||
* @param string|int $key
|
||||
*/
|
||||
public function get($key): FunctionDefinition
|
||||
public function get($key): FunctionObject
|
||||
{
|
||||
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);
|
||||
return $this->function($key);
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
foreach (array_keys($this->callables) as $key) {
|
||||
yield $key => $this->function($key)['definition'];
|
||||
yield $key => $this->function($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $key
|
||||
* @return array{definition: FunctionDefinition, callback: callable}
|
||||
*/
|
||||
private function function($key): array
|
||||
private function function($key): FunctionObject
|
||||
{
|
||||
/** @infection-ignore-all */
|
||||
return $this->functions[$key] ??= [
|
||||
'callback' => $this->callables[$key],
|
||||
'definition' => $this->functionDefinitionRepository->for($this->callables[$key]),
|
||||
];
|
||||
return $this->functions[$key] ??= new FunctionObject(
|
||||
$this->functionDefinitionRepository->for($this->callables[$key]),
|
||||
$this->callables[$key]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -70,8 +70,8 @@ final class ConstructorObjectBuilderFactory implements ObjectBuilderFactory
|
||||
$methods = $class->methods();
|
||||
|
||||
foreach ($this->constructors as $constructor) {
|
||||
if ($constructor->returnType()->matches($type)) {
|
||||
$builders[] = new FunctionObjectBuilder($constructor, $this->constructors->callback($constructor));
|
||||
if ($constructor->definition()->returnType()->matches($type)) {
|
||||
$builders[] = new FunctionObjectBuilder($constructor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,15 +56,17 @@ final class DateTimeObjectBuilderFactory implements ObjectBuilderFactory
|
||||
$this->builders[$key] = [];
|
||||
|
||||
foreach ($this->functions as $function) {
|
||||
if (! $function->returnType()->matches($type)) {
|
||||
$definition = $function->definition();
|
||||
|
||||
if (! $definition->returnType()->matches($type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($function->parameters()) === 1) {
|
||||
if (count($definition->parameters()) === 1) {
|
||||
$overridesDefault = true;
|
||||
}
|
||||
|
||||
$this->builders[$key][] = new FunctionObjectBuilder($function, $this->functions->callback($function));
|
||||
$this->builders[$key][] = new FunctionObjectBuilder($function);
|
||||
}
|
||||
|
||||
if (! $overridesDefault) {
|
||||
|
@ -4,40 +4,33 @@ declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use CuyZ\Valinor\Definition\FunctionDefinition;
|
||||
use CuyZ\Valinor\Definition\FunctionObject;
|
||||
use CuyZ\Valinor\Mapper\Tree\Message\UserlandError;
|
||||
use Exception;
|
||||
|
||||
/** @internal */
|
||||
final class FunctionObjectBuilder implements ObjectBuilder
|
||||
{
|
||||
private FunctionDefinition $function;
|
||||
|
||||
/** @var callable(): object */
|
||||
private $callback;
|
||||
private FunctionObject $function;
|
||||
|
||||
private Arguments $arguments;
|
||||
|
||||
/**
|
||||
* @param callable(): object $callback
|
||||
*/
|
||||
public function __construct(FunctionDefinition $function, callable $callback)
|
||||
public function __construct(FunctionObject $function)
|
||||
{
|
||||
$this->function = $function;
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
public function describeArguments(): Arguments
|
||||
{
|
||||
return $this->arguments ??= Arguments::fromParameters($this->function->parameters());
|
||||
return $this->arguments ??= Arguments::fromParameters($this->function->definition()->parameters());
|
||||
}
|
||||
|
||||
public function build(array $arguments): object
|
||||
{
|
||||
$arguments = new MethodArguments($this->function->parameters(), $arguments);
|
||||
$arguments = new MethodArguments($this->function->definition()->parameters(), $arguments);
|
||||
|
||||
try {
|
||||
return ($this->callback)(...$arguments);
|
||||
return ($this->function->callback())(...$arguments);
|
||||
} catch (Exception $exception) {
|
||||
throw UserlandError::from($exception);
|
||||
}
|
||||
@ -45,6 +38,6 @@ final class FunctionObjectBuilder implements ObjectBuilder
|
||||
|
||||
public function signature(): string
|
||||
{
|
||||
return $this->function->signature();
|
||||
return $this->function->definition()->signature();
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ final class ObjectImplementations
|
||||
throw new CannotResolveObjectType($name);
|
||||
}
|
||||
|
||||
return $this->functions->get($name);
|
||||
return $this->functions->get($name)->definition();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,11 +72,8 @@ final class ObjectImplementations
|
||||
*/
|
||||
private function call(string $name, array $arguments): string
|
||||
{
|
||||
$function = $this->functions->get($name);
|
||||
$callback = $this->functions->callback($function);
|
||||
|
||||
try {
|
||||
$signature = $callback(...$arguments);
|
||||
$signature = ($this->functions->get($name)->callback())(...$arguments);
|
||||
} catch (Exception $exception) {
|
||||
throw new ObjectImplementationCallbackError($name, $exception);
|
||||
}
|
||||
@ -93,7 +90,7 @@ final class ObjectImplementations
|
||||
*/
|
||||
private function implementations(string $name): array
|
||||
{
|
||||
$function = $this->functions->get($name);
|
||||
$function = $this->functions->get($name)->definition();
|
||||
|
||||
try {
|
||||
$type = $this->typeParser->parse($name);
|
||||
|
@ -31,7 +31,7 @@ final class ValueAlteringNodeBuilder implements NodeBuilder
|
||||
$value = $node->value();
|
||||
|
||||
foreach ($this->functions as $function) {
|
||||
$parameters = $function->parameters();
|
||||
$parameters = $function->definition()->parameters();
|
||||
|
||||
if (count($parameters) === 0) {
|
||||
continue;
|
||||
@ -43,7 +43,7 @@ final class ValueAlteringNodeBuilder implements NodeBuilder
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = ($this->functions->callback($function))($value);
|
||||
$value = ($function->callback())($value);
|
||||
$node = $node->withValue($value);
|
||||
}
|
||||
|
||||
|
@ -4,10 +4,7 @@ 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;
|
||||
|
||||
@ -15,26 +12,6 @@ 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(), [
|
||||
@ -47,4 +24,14 @@ final class FunctionsContainerTest extends TestCase
|
||||
self::assertArrayHasKey('foo', $functions);
|
||||
self::assertArrayHasKey('bar', $functions);
|
||||
}
|
||||
|
||||
public function test_function_object_remains_the_same(): void
|
||||
{
|
||||
$functions = (new FunctionsContainer(new FakeFunctionDefinitionRepository(), [fn () => 'foo']));
|
||||
|
||||
$functionA = $functions->get(0);
|
||||
$functionB = $functions->get(0);
|
||||
|
||||
self::assertSame($functionA, $functionB);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Mapper\Object;
|
||||
|
||||
use CuyZ\Valinor\Definition\FunctionObject;
|
||||
use CuyZ\Valinor\Mapper\Object\FunctionObjectBuilder;
|
||||
use CuyZ\Valinor\Tests\Fake\Definition\FakeFunctionDefinition;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@ -13,7 +14,7 @@ final class FunctionObjectBuilderTest extends TestCase
|
||||
{
|
||||
public function test_arguments_instance_stays_the_same(): void
|
||||
{
|
||||
$objectBuilder = new FunctionObjectBuilder(FakeFunctionDefinition::new(), fn () => new stdClass());
|
||||
$objectBuilder = new FunctionObjectBuilder(new FunctionObject(FakeFunctionDefinition::new(), fn () => new stdClass()));
|
||||
|
||||
$argumentsA = $objectBuilder->describeArguments();
|
||||
$argumentsB = $objectBuilder->describeArguments();
|
||||
|
Loading…
Reference in New Issue
Block a user