Skip to content

Commit

Permalink
misc!: allow object builder to yield arguments without source
Browse files Browse the repository at this point in the history
The `Argument` class must now be instantiated with one of the `required`
or `optional` static constructors.
  • Loading branch information
romm committed Jan 7, 2022
1 parent e834cdc commit 8a74147
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 173 deletions.
47 changes: 37 additions & 10 deletions src/Mapper/Object/Argument.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,41 @@ final class Argument
private Type $type;

/** @var mixed */
private $value;
private $defaultValue;

private ?Attributes $attributes;
private bool $isRequired = true;

/**
* @param mixed $value
*/
public function __construct(string $name, Type $type, $value, Attributes $attributes = null)
private Attributes $attributes;

private function __construct(string $name, Type $type)
{
$this->name = $name;
$this->type = $type;
$this->value = $value;
$this->attributes = $attributes;
}

public static function required(string $name, Type $type): self
{
return new self($name, $type);
}

/**
* @param mixed $defaultValue
*/
public static function optional(string $name, Type $type, $defaultValue): self
{
$instance = new self($name, $type);
$instance->defaultValue = $defaultValue;
$instance->isRequired = false;

return $instance;
}

public function withAttributes(Attributes $attributes): self
{
$clone = clone $this;
$clone->attributes = $attributes;

return $clone;
}

public function name(): string
Expand All @@ -43,9 +65,14 @@ public function type(): Type
/**
* @return mixed
*/
public function value()
public function defaultValue()
{
return $this->defaultValue;
}

public function isRequired(): bool
{
return $this->value;
return $this->isRequired;
}

public function attributes(): Attributes
Expand Down
43 changes: 30 additions & 13 deletions src/Mapper/Object/DateTimeObjectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
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;
use CuyZ\Valinor\Type\Types\ShapedArrayElement;
use CuyZ\Valinor\Type\Types\ShapedArrayType;
use CuyZ\Valinor\Type\Types\StringValueType;
use CuyZ\Valinor\Type\Types\UnionType;
use DateTime;
use DateTimeImmutable;
Expand All @@ -24,6 +28,8 @@ 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;

Expand All @@ -35,24 +41,35 @@ public function __construct(string $className)
$this->className = $className;
}

public function describeArguments($source): iterable
public function describeArguments(): iterable
{
$datetime = $source;
$format = null;

if (is_array($source)) {
$datetime = $source['datetime'] ?? null;
$format = $source['format'] ?? null;
}

yield new Argument('datetime', new UnionType(PositiveIntegerType::get(), NonEmptyStringType::get()), $datetime);
yield new Argument('format', new UnionType(NullType::get(), NonEmptyStringType::get()), $format);
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
),
)
);

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

public function build(array $arguments): DateTimeInterface
{
$datetime = $arguments['datetime'];
$format = $arguments['format'];
$datetime = $arguments['value'];
$format = null;

if (is_array($datetime)) {
$format = $datetime['format'];
$datetime = $datetime['datetime'];
}

assert(is_string($datetime) || is_int($datetime));
assert(is_string($format) || is_null($format));
Expand Down
60 changes: 10 additions & 50 deletions src/Mapper/Object/MethodObjectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,14 @@
use CuyZ\Valinor\Mapper\Object\Exception\ConstructorMethodIsNotStatic;
use CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorMethodClassReturnType;
use CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorMethodReturnType;
use CuyZ\Valinor\Mapper\Object\Exception\InvalidSourceForObject;
use CuyZ\Valinor\Mapper\Object\Exception\MethodNotFound;
use CuyZ\Valinor\Mapper\Object\Exception\MissingMethodArgument;
use CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage;
use CuyZ\Valinor\Type\Types\ClassType;
use Exception;

use function array_key_exists;
use function array_keys;
use function array_values;
use function count;
use function is_a;
use function is_array;
use function is_iterable;
use function iterator_to_array;

final class MethodObjectBuilder implements ObjectBuilder
{
Expand Down Expand Up @@ -66,17 +59,14 @@ public function __construct(ClassDefinition $class, string $methodName)
}
}

public function describeArguments($source): iterable
public function describeArguments(): iterable
{
$source = $this->transformSource($source);

foreach ($this->method->parameters() as $parameter) {
$name = $parameter->name();
$type = $parameter->type();
$attributes = $parameter->attributes();
$value = array_key_exists($name, $source) ? $source[$name] : $parameter->defaultValue();
$argument = $parameter->isOptional()
? Argument::optional($parameter->name(), $parameter->type(), $parameter->defaultValue())
: Argument::required($parameter->name(), $parameter->type());

yield new Argument($name, $type, $value, $attributes);
yield $argument->withAttributes($parameter->attributes());
}
}

Expand All @@ -92,48 +82,18 @@ public function build(array $arguments): object
$methodName = $this->method->name();

try {
// @PHP8.0 `array_values` can be removed
$arguments = array_values($arguments);

if (! $this->method->isStatic()) {
// @PHP8.0 `array_values` can be removed
/** @infection-ignore-all */
return new $className(...array_values($arguments));
return new $className(...$arguments);
}

// @PHP8.0 `array_values` can be removed
/** @infection-ignore-all */
return $className::$methodName(...array_values($arguments)); // @phpstan-ignore-line
return $className::$methodName(...$arguments); // @phpstan-ignore-line
} catch (Exception $exception) {
throw ThrowableMessage::from($exception);
}
}

/**
* @param mixed $source
* @return mixed[]
*/
private function transformSource($source): array
{
if ($source === null) {
return [];
}

if (is_iterable($source) && ! is_array($source)) {
$source = iterator_to_array($source);
}

$parameters = $this->method->parameters();

if (count($parameters) === 1) {
$name = array_keys(iterator_to_array($parameters))[0];

if (! is_array($source) || ! array_key_exists($name, $source)) {
$source = [$name => $source];
}
}

if (! is_array($source)) {
throw new InvalidSourceForObject($source);
}

return $source;
}
}
3 changes: 1 addition & 2 deletions src/Mapper/Object/ObjectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
interface ObjectBuilder
{
/**
* @param mixed $source
* @return iterable<Argument>
*/
public function describeArguments($source): iterable;
public function describeArguments(): iterable;

/**
* @param array<string, mixed> $arguments
Expand Down
49 changes: 5 additions & 44 deletions src/Mapper/Object/ReflectionObjectBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,9 @@
namespace CuyZ\Valinor\Mapper\Object;

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

use function array_key_exists;
use function array_keys;
use function count;
use function is_array;
use function iterator_to_array;

final class ReflectionObjectBuilder implements ObjectBuilder
{
Expand All @@ -23,17 +18,14 @@ public function __construct(ClassDefinition $class)
$this->class = $class;
}

public function describeArguments($source): iterable
public function describeArguments(): iterable
{
$source = $this->transformSource($source);

foreach ($this->class->properties() as $property) {
$name = $property->name();
$type = $property->type();
$attributes = $property->attributes();
$value = array_key_exists($name, $source) ? $source[$name] : $property->defaultValue();
$argument = $property->hasDefaultValue()
? Argument::optional($property->name(), $property->type(), $property->defaultValue())
: Argument::required($property->name(), $property->type());

yield new Argument($name, $type, $value, $attributes);
yield $argument->withAttributes($property->attributes());
}
}

Expand All @@ -57,35 +49,4 @@ public function build(array $arguments): object

return $object;
}

/**
* @param mixed $source
* @return mixed[]
*/
private function transformSource($source): array
{
if ($source === null) {
return [];
}

if (is_iterable($source) && ! is_array($source)) {
$source = iterator_to_array($source);
}

$properties = $this->class->properties();

if (count($properties) === 1) {
$name = array_keys(iterator_to_array($properties))[0];

if (! is_array($source) || ! array_key_exists($name, $source)) {
$source = [$name => $source];
}
}

if (! is_array($source)) {
throw new InvalidSourceForObject($source);
}

return $source;
}
}
Loading

0 comments on commit 8a74147

Please sign in to comment.