Skip to content

Commit

Permalink
misc: introduce layer for object builder arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
romm committed May 21, 2022
1 parent 11e1262 commit 48f9362
Show file tree
Hide file tree
Showing 15 changed files with 277 additions and 58 deletions.
60 changes: 60 additions & 0 deletions src/Mapper/Object/Arguments.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Mapper\Object;

use Countable;
use CuyZ\Valinor\Mapper\Object\Exception\InvalidArgumentIndex;
use IteratorAggregate;
use Traversable;

/**
* @internal
*
* @implements IteratorAggregate<Argument>
*/
final class Arguments implements IteratorAggregate, Countable
{
/** @var Argument[] */
private array $arguments;

public function __construct(Argument ...$arguments)
{
$this->arguments = $arguments;
}

public function at(int $index): Argument
{
if ($index >= count($this->arguments)) {
throw new InvalidArgumentIndex($index, $this);
}

return $this->arguments[$index];
}

public function signature(): string
{
$parameters = array_map(
fn (Argument $argument) => $argument->isRequired()
? "{$argument->name()}: {$argument->type()}"
: "{$argument->name()}?: {$argument->type()}",
$this->arguments
);

return 'array{' . implode(', ', $parameters) . '}';
}

public function count(): int
{
return count($this->arguments);
}

/**
* @return Traversable<Argument>
*/
public function getIterator(): Traversable
{
yield from $this->arguments;
}
}
37 changes: 18 additions & 19 deletions src/Mapper/Object/DateTimeObjectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace CuyZ\Valinor\Mapper\Object;

use CuyZ\Valinor\Mapper\Object\Exception\CannotParseToDateTime;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\NonEmptyStringType;
use CuyZ\Valinor\Type\Types\NullType;
use CuyZ\Valinor\Type\Types\PositiveIntegerType;
Expand All @@ -29,11 +28,11 @@ final class DateTimeObjectBuilder implements ObjectBuilder
public const DATE_MYSQL = 'Y-m-d H:i:s';
public const DATE_PGSQL = 'Y-m-d H:i:s.u';

private static Type $argumentType;

/** @var class-string<DateTime|DateTimeImmutable> */
private string $className;

private Arguments $arguments;

/**
* @param class-string<DateTime|DateTimeImmutable> $className
*/
Expand All @@ -42,24 +41,24 @@ public function __construct(string $className)
$this->className = $className;
}

public function describeArguments(): iterable
public function describeArguments(): Arguments
{
self::$argumentType ??= new UnionType(
new UnionType(PositiveIntegerType::get(), NonEmptyStringType::get()),
new ShapedArrayType(
new ShapedArrayElement(
new StringValueType('datetime'),
new UnionType(PositiveIntegerType::get(), NonEmptyStringType::get())
),
new ShapedArrayElement(
new StringValueType('format'),
new UnionType(NullType::get(), NonEmptyStringType::get()),
true
),
)
return $this->arguments ??= new Arguments(
Argument::required('value', new UnionType(
new UnionType(PositiveIntegerType::get(), NonEmptyStringType::get()),
new ShapedArrayType(
new ShapedArrayElement(
new StringValueType('datetime'),
new UnionType(PositiveIntegerType::get(), NonEmptyStringType::get())
),
new ShapedArrayElement(
new StringValueType('format'),
new UnionType(NullType::get(), NonEmptyStringType::get()),
true
),
)
))
);

yield Argument::required('value', self::$argumentType);
}

public function build(array $arguments): DateTimeInterface
Expand Down
22 changes: 22 additions & 0 deletions src/Mapper/Object/Exception/InvalidArgumentIndex.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Mapper\Object\Exception;

use CuyZ\Valinor\Mapper\Object\Arguments;
use OutOfBoundsException;

/** @internal */
final class InvalidArgumentIndex extends OutOfBoundsException
{
public function __construct(int $index, Arguments $arguments)
{
$max = $arguments->count() - 1;

parent::__construct(
"Index $index is out of range, it should be between 0 and $max.",
1648672136
);
}
}
25 changes: 17 additions & 8 deletions src/Mapper/Object/FunctionObjectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
namespace CuyZ\Valinor\Mapper\Object;

use CuyZ\Valinor\Definition\FunctionDefinition;
use CuyZ\Valinor\Definition\ParameterDefinition;
use CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage;
use Exception;

use function array_map;
use function array_values;
use function iterator_to_array;

/** @internal */
final class FunctionObjectBuilder implements ObjectBuilder
{
Expand All @@ -16,6 +21,8 @@ final class FunctionObjectBuilder implements ObjectBuilder
/** @var callable(): object */
private $callback;

private Arguments $arguments;

/**
* @param callable(): object $callback
*/
Expand All @@ -25,15 +32,17 @@ public function __construct(FunctionDefinition $function, callable $callback)
$this->callback = $callback;
}

public function describeArguments(): iterable
public function describeArguments(): Arguments
{
foreach ($this->function->parameters() as $parameter) {
$argument = $parameter->isOptional()
? Argument::optional($parameter->name(), $parameter->type(), $parameter->defaultValue())
: Argument::required($parameter->name(), $parameter->type());

yield $argument->withAttributes($parameter->attributes());
}
return $this->arguments ??= new Arguments(
...array_map(function (ParameterDefinition $parameter) {
$argument = $parameter->isOptional()
? Argument::optional($parameter->name(), $parameter->type(), $parameter->defaultValue())
: Argument::required($parameter->name(), $parameter->type());

return $argument->withAttributes($parameter->attributes());
}, array_values(iterator_to_array($this->function->parameters()))) // @PHP8.1 array unpacking
);
}

public function build(array $arguments): object
Expand Down
25 changes: 17 additions & 8 deletions src/Mapper/Object/MethodObjectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,27 @@

use CuyZ\Valinor\Definition\ClassDefinition;
use CuyZ\Valinor\Definition\MethodDefinition;
use CuyZ\Valinor\Definition\ParameterDefinition;
use CuyZ\Valinor\Mapper\Object\Exception\ConstructorMethodIsNotPublic;
use CuyZ\Valinor\Mapper\Object\Exception\ConstructorMethodIsNotStatic;
use CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorMethodClassReturnType;
use CuyZ\Valinor\Mapper\Object\Exception\MethodNotFound;
use CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage;
use Exception;

use function array_map;
use function array_values;
use function iterator_to_array;

/** @api */
final class MethodObjectBuilder implements ObjectBuilder
{
private ClassDefinition $class;

private MethodDefinition $method;

private Arguments $arguments;

public function __construct(ClassDefinition $class, string $methodName)
{
$methods = $class->methods();
Expand Down Expand Up @@ -48,15 +55,17 @@ public function __construct(ClassDefinition $class, string $methodName)
}
}

public function describeArguments(): iterable
public function describeArguments(): Arguments
{
foreach ($this->method->parameters() as $parameter) {
$argument = $parameter->isOptional()
? Argument::optional($parameter->name(), $parameter->type(), $parameter->defaultValue())
: Argument::required($parameter->name(), $parameter->type());

yield $argument->withAttributes($parameter->attributes());
}
return $this->arguments ??= new Arguments(
...array_map(function (ParameterDefinition $parameter) {
$argument = $parameter->isOptional()
? Argument::optional($parameter->name(), $parameter->type(), $parameter->defaultValue())
: Argument::required($parameter->name(), $parameter->type());

return $argument->withAttributes($parameter->attributes());
}, array_values(iterator_to_array($this->method->parameters()))) // @PHP8.1 array unpacking
);
}

public function build(array $arguments): object
Expand Down
5 changes: 1 addition & 4 deletions src/Mapper/Object/ObjectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
/** @internal */
interface ObjectBuilder
{
/**
* @return iterable<Argument>
*/
public function describeArguments(): iterable;
public function describeArguments(): Arguments;

/**
* @param array<string, mixed> $arguments
Expand Down
8 changes: 4 additions & 4 deletions src/Mapper/Object/ObjectBuilderFilterer.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public function filter($source, ObjectBuilder ...$builders): ObjectBuilder
*/
private function filledArguments(ObjectBuilder $builder, $source)
{
$arguments = [...$builder->describeArguments()];
$arguments = $builder->describeArguments();

if (! is_array($source)) {
return count($arguments) === 1;
Expand All @@ -63,10 +63,10 @@ private function filledArguments(ObjectBuilder $builder, $source)
/** @infection-ignore-all */
$filled = 0;

foreach ($arguments as $parameter) {
if (isset($source[$parameter->name()])) {
foreach ($arguments as $argument) {
if (isset($source[$argument->name()])) {
$filled++;
} elseif ($parameter->isRequired()) {
} elseif ($argument->isRequired()) {
return false;
}
}
Expand Down
24 changes: 16 additions & 8 deletions src/Mapper/Object/ReflectionObjectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,37 @@
namespace CuyZ\Valinor\Mapper\Object;

use CuyZ\Valinor\Definition\ClassDefinition;
use CuyZ\Valinor\Definition\PropertyDefinition;
use CuyZ\Valinor\Mapper\Object\Exception\MissingPropertyArgument;

use function array_key_exists;
use function array_map;
use function array_values;
use function iterator_to_array;

/** @api */
final class ReflectionObjectBuilder implements ObjectBuilder
{
private ClassDefinition $class;

private Arguments $arguments;

public function __construct(ClassDefinition $class)
{
$this->class = $class;
}

public function describeArguments(): iterable
public function describeArguments(): Arguments
{
foreach ($this->class->properties() as $property) {
$argument = $property->hasDefaultValue()
? Argument::optional($property->name(), $property->type(), $property->defaultValue())
: Argument::required($property->name(), $property->type());

yield $argument->withAttributes($property->attributes());
}
return $this->arguments ??= new Arguments(
...array_map(function (PropertyDefinition $property) {
$argument = $property->hasDefaultValue()
? Argument::optional($property->name(), $property->type(), $property->defaultValue())
: Argument::required($property->name(), $property->type());

return $argument->withAttributes($property->attributes());
}, array_values(iterator_to_array($this->class->properties()))) // @PHP8.1 array unpacking
);
}

public function build(array $arguments): object
Expand Down
10 changes: 5 additions & 5 deletions src/Mapper/Tree/Builder/ClassNodeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace CuyZ\Valinor\Mapper\Tree\Builder;

use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
use CuyZ\Valinor\Mapper\Object\Argument;
use CuyZ\Valinor\Mapper\Object\Arguments;
use CuyZ\Valinor\Mapper\Object\Exception\InvalidSourceForObject;
use CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\SuitableObjectBuilderNotFound;
Expand Down Expand Up @@ -58,9 +58,9 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
$source = $shell->value();

$builder = $this->builder($source, ...$classTypes);
$arguments = [...$builder->describeArguments()];
$arguments = $builder->describeArguments();

$source = $this->transformSource($source, ...$arguments);
$source = $this->transformSource($source, $arguments);
$children = [];

foreach ($arguments as $argument) {
Expand Down Expand Up @@ -129,7 +129,7 @@ private function builder($source, ClassType ...$classTypes): ObjectBuilder
* @param mixed $source
* @return mixed[]
*/
private function transformSource($source, Argument ...$arguments): array
private function transformSource($source, Arguments $arguments): array
{
if ($source === null || count($arguments) === 0) {
return [];
Expand All @@ -140,7 +140,7 @@ private function transformSource($source, Argument ...$arguments): array
}

if (count($arguments) === 1) {
$name = $arguments[0]->name();
$name = $arguments->at(0)->name();

if (! is_array($source) || ! array_key_exists($name, $source)) {
$source = [$name => $source];
Expand Down
5 changes: 3 additions & 2 deletions tests/Fake/Mapper/Object/FakeObjectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@

namespace CuyZ\Valinor\Tests\Fake\Mapper\Object;

use CuyZ\Valinor\Mapper\Object\Arguments;
use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
use stdClass;

final class FakeObjectBuilder implements ObjectBuilder
{
public function describeArguments(): iterable
public function describeArguments(): Arguments
{
return [];
return new Arguments();
}

public function build(array $arguments): object
Expand Down
Loading

0 comments on commit 48f9362

Please sign in to comment.