Skip to content

Commit

Permalink
Merge pull request #39 from sunrise-php/release/v3.12.0
Browse files Browse the repository at this point in the history
v3.12.0
  • Loading branch information
fenric authored Oct 28, 2024
2 parents df8d6f7 + 8d54458 commit 066d740
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 53 deletions.
51 changes: 51 additions & 0 deletions src/Annotation/DefaultValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/**
* It's free open-source software released under the MIT License.
*
* @author Anatoly Nekhay <[email protected]>
* @copyright Copyright (c) 2021, Anatoly Nekhay
* @license https://github.com/sunrise-php/hydrator/blob/master/LICENSE
* @link https://github.com/sunrise-php/hydrator
*/

declare(strict_types=1);

namespace Sunrise\Hydrator\Annotation;

use Attribute;

/**
* @Annotation
* @Target({"PROPERTY"})
* @NamedArgumentConstructor
*
* @Attributes({
* @Attribute("value", type="mixed", required=true),
* })
*
* @since 3.12.0
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class DefaultValue
{

/**
* The attribute value
*
* @var mixed
*
* @readonly
*/
public $value;

/**
* Constructor of the class
*
* @param mixed $value
*/
public function __construct($value)
{
$this->value = $value;
}
}
7 changes: 7 additions & 0 deletions src/Annotation/Subtype.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@
class Subtype
{

/**
* @var mixed
*
* @internal
*/
public $holder = null;

/**
* @var non-empty-string
*
Expand Down
16 changes: 10 additions & 6 deletions src/Exception/InvalidObjectException.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,11 @@ final public static function unsupportedParameterType(Type $type, ReflectionPara
*
* @since 3.2.0
*/
// phpcs:ignore Generic.Files.LineLength
final public static function unsupportedMethodParameterType(Type $type, ReflectionParameter $parameter, ReflectionMethod $method): self
{
final public static function unsupportedMethodParameterType(
Type $type,
ReflectionParameter $parameter,
ReflectionMethod $method
): self {
return new self(sprintf(
'The parameter {%s::%s($%s[%d])} is associated with an unsupported type {%s}.',
$method->getDeclaringClass()->getName(),
Expand All @@ -134,9 +136,11 @@ final public static function unsupportedMethodParameterType(Type $type, Reflecti
*
* @since 3.2.0
*/
// phpcs:ignore Generic.Files.LineLength
final public static function unsupportedFunctionParameterType(Type $type, ReflectionParameter $parameter, ReflectionFunctionAbstract $function): self
{
final public static function unsupportedFunctionParameterType(
Type $type,
ReflectionParameter $parameter,
ReflectionFunctionAbstract $function
): self {
return new self(sprintf(
'The parameter {%s($%s[%d])} is associated with an unsupported type {%s}.',
$function->getName(),
Expand Down
39 changes: 26 additions & 13 deletions src/Hydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use ReflectionClass;
use Sunrise\Hydrator\Annotation\Alias;
use Sunrise\Hydrator\Annotation\Context;
use Sunrise\Hydrator\Annotation\DefaultValue;
use Sunrise\Hydrator\Annotation\Ignore;
use Sunrise\Hydrator\AnnotationReader\BuiltinAnnotationReader;
use Sunrise\Hydrator\AnnotationReader\DoctrineAnnotationReader;
Expand Down Expand Up @@ -159,8 +160,10 @@ public function addTypeConverter(TypeConverterInterface ...$typeConverters): sel
$this->typeConverters[] = $typeConverter;
}

// phpcs:ignore Generic.Files.LineLength
usort($this->typeConverters, static fn(TypeConverterInterface $a, TypeConverterInterface $b): int => $b->getWeight() <=> $a->getWeight());
usort($this->typeConverters, static fn(
TypeConverterInterface $a,
TypeConverterInterface $b
): int => $b->getWeight() <=> $a->getWeight());

return $this;
}
Expand Down Expand Up @@ -196,9 +199,9 @@ public function castValue($value, Type $type, array $path = [], array $context =
*/
public function hydrate($object, array $data, array $path = [], array $context = []): object
{
[$object, $class] = $this->instantObject($object);
[$object, $class] = self::instantObject($object);
$properties = $class->getProperties();
$constructorDefaultValues = $this->getClassConstructorDefaultValues($class);
$constructorDefaultValues = self::getConstructorDefaultValues($class);

$violations = [];
foreach ($properties as $property) {
Expand All @@ -216,8 +219,8 @@ public function hydrate($object, array $data, array $path = [], array $context =
continue;
}

// phpcs:ignore Generic.Files.LineLength
$key = $this->annotationReader->getAnnotations(Alias::class, $property)->current()->value ?? $property->getName();
$key = $this->annotationReader->getAnnotations(Alias::class, $property)->current()->value
?? $property->getName();

if (array_key_exists($key, $data) === false) {
if ($property->isInitialized($object)) {
Expand All @@ -229,6 +232,12 @@ public function hydrate($object, array $data, array $path = [], array $context =
continue;
}

$defaultValue = $this->annotationReader->getAnnotations(DefaultValue::class, $property)->current();
if ($defaultValue !== null) {
$property->setValue($object, $defaultValue->value);
continue;
}

$violations[] = InvalidValueException::mustBeProvided([...$path, $key]);
continue;
}
Expand All @@ -243,7 +252,7 @@ public function hydrate($object, array $data, array $path = [], array $context =
}
}

if (!empty($violations)) {
if ($violations !== []) {
throw new InvalidDataException('Invalid data', $violations);
}

Expand All @@ -264,8 +273,10 @@ public function hydrateWithJson($object, string $json, int $flags = 0, int $dept
try {
$data = json_decode($json, true, $depth, $flags | JSON_BIGINT_AS_STRING | JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
// phpcs:ignore Generic.Files.LineLength
throw new InvalidDataException(sprintf('The JSON is invalid and couldn‘t be decoded due to: %s', $e->getMessage()));
throw new InvalidDataException(sprintf(
'The JSON is invalid and couldn‘t be decoded due to: %s',
$e->getMessage(),
));
}

if (!is_array($data)) {
Expand All @@ -286,16 +297,18 @@ public function hydrateWithJson($object, string $json, int $flags = 0, int $dept
*
* @template T of object
*/
private function instantObject($object): array
private static function instantObject($object): array
{
if (is_object($object)) {
return [$object, new ReflectionClass($object)];
}

/** @psalm-suppress DocblockTypeContradiction */
if (!is_string($object)) {
// phpcs:ignore Generic.Files.LineLength
throw new TypeError(sprintf('Argument #1 ($object) must be of type object or string, %s given', gettype($object)));
throw new TypeError(sprintf(
'Argument #1 ($object) must be of type object or string, %s given',
gettype($object),
));
}

if (!class_exists($object)) {
Expand All @@ -319,7 +332,7 @@ private function instantObject($object): array
*
* @template T of object
*/
private function getClassConstructorDefaultValues(ReflectionClass $class): array
private static function getConstructorDefaultValues(ReflectionClass $class): array
{
$constructor = $class->getConstructor();
if ($constructor === null) {
Expand Down
55 changes: 33 additions & 22 deletions src/TypeConverter/ArrayAccessTypeConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ public function setHydrator(HydratorInterface $hydrator): void
*/
public function castValue($value, Type $type, array $path, array $context): Generator
{
$containerName = $type->getName();
if (!is_subclass_of($containerName, ArrayAccess::class)) {
$typeName = $type->getName();
if (!is_subclass_of($typeName, ArrayAccess::class)) {
return;
}

$containerReflection = new ReflectionClass($containerName);
$containerReflection = new ReflectionClass($typeName);
if (!$containerReflection->isInstantiable()) {
throw InvalidObjectException::unsupportedType($type);
}
Expand All @@ -105,8 +105,8 @@ public function castValue($value, Type $type, array $path, array $context): Gene
throw InvalidValueException::mustBeArray($path);
}

// phpcs:ignore Generic.Files.LineLength
$subtype = $this->annotationReader->getAnnotations(Subtype::class, $type->getHolder())->current() ?? $this->getContainerSubtype($containerReflection);
$subtype = $this->annotationReader->getAnnotations(Subtype::class, $type->getHolder())->current()
?? self::getContainerSubtype($containerReflection);

if ($subtype === null) {
$counter = 0;
Expand All @@ -122,17 +122,19 @@ public function castValue($value, Type $type, array $path, array $context): Gene
return yield $container;
}

if (isset($subtype->limit) && count($value) > $subtype->limit) {
if ($subtype->limit !== null && count($value) > $subtype->limit) {
throw InvalidValueException::arrayOverflow($path, $subtype->limit);
}

$subtype->holder ??= $type->getHolder();

$counter = 0;
$violations = [];
foreach ($value as $key => $element) {
try {
$container[$key] = $this->hydrator->castValue(
$element,
new Type($type->getHolder(), $subtype->name, $subtype->allowsNull),
new Type($subtype->holder, $subtype->name, $subtype->allowsNull),
[...$path, $key],
$context,
);
Expand All @@ -148,11 +150,11 @@ public function castValue($value, Type $type, array $path, array $context): Gene
}
}

if ($violations === []) {
return yield $container;
if ($violations !== []) {
throw new InvalidDataException('Invalid data', $violations);
}

throw new InvalidDataException('Invalid data', $violations);
yield $container;
}

/**
Expand All @@ -166,37 +168,46 @@ public function getWeight(): int
/**
* Gets a subtype from the given container's constructor
*
* @param ReflectionClass $container
* @param ReflectionClass<ArrayAccess> $class
*
* @return Subtype|null
*
* @codeCoverageIgnore
*/
private function getContainerSubtype(ReflectionClass $container): ?Subtype
private static function getContainerSubtype(ReflectionClass $class): ?Subtype
{
$constructor = $container->getConstructor();
$constructor = $class->getConstructor();
if ($constructor === null) {
return null;
}

$parameters = $constructor->getParameters();
if ($parameters === []) {
$constructorParameters = $constructor->getParameters();
if ($constructorParameters === []) {
return null;
}

$parameter = end($parameters);
if ($parameter->isVariadic() === false) {
$lastConstructorParameter = end($constructorParameters);
if ($lastConstructorParameter->isVariadic() === false) {
return null;
}

$type = $parameter->getType();
if ($type === null) {
$lastConstructorParameterType = $lastConstructorParameter->getType();
if ($lastConstructorParameterType === null) {
return null;
}

/** @var non-empty-string $name */
$name = ($type instanceof ReflectionNamedType) ? $type->getName() : (string) $type;
/** @var non-empty-string $lastConstructorParameterTypeName */
$lastConstructorParameterTypeName = ($lastConstructorParameterType instanceof ReflectionNamedType)
? $lastConstructorParameterType->getName()
: (string) $lastConstructorParameterType;

$subtype = new Subtype(
$lastConstructorParameterTypeName,
$lastConstructorParameterType->allowsNull(),
);

$subtype->holder = $lastConstructorParameter;

return new Subtype($name, $type->allowsNull());
return $subtype;
}
}
8 changes: 4 additions & 4 deletions src/TypeConverter/ArrayTypeConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public function castValue($value, Type $type, array $path, array $context): Gene
return yield $value;
}

if (isset($subtype->limit) && count($value) > $subtype->limit) {
if ($subtype->limit !== null && count($value) > $subtype->limit) {
throw InvalidValueException::arrayOverflow($path, $subtype->limit);
}

Expand All @@ -116,11 +116,11 @@ public function castValue($value, Type $type, array $path, array $context): Gene
}
}

if ($violations === []) {
return yield $value;
if ($violations !== []) {
throw new InvalidDataException('Invalid data', $violations);
}

throw new InvalidDataException('Invalid data', $violations);
yield $value;
}

/**
Expand Down
7 changes: 1 addition & 6 deletions src/TypeConverter/BackedEnumTypeConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use BackedEnum;
use Generator;
use ReflectionEnum;
use ReflectionNamedType;
use Sunrise\Hydrator\Dictionary\BuiltinType;
use Sunrise\Hydrator\Exception\InvalidValueException;
use Sunrise\Hydrator\Type;
Expand Down Expand Up @@ -54,11 +53,7 @@ public function castValue($value, Type $type, array $path, array $context): Gene
return;
}

/** @var ReflectionNamedType $enumType */
$enumType = (new ReflectionEnum($enumName))->getBackingType();

/** @var BuiltinType::INT|BuiltinType::STRING $enumTypeName */
$enumTypeName = $enumType->getName();
$enumTypeName = (string) (new ReflectionEnum($enumName))->getBackingType();

if (is_string($value)) {
$value = trim($value);
Expand Down
5 changes: 3 additions & 2 deletions src/TypeConverter/TimestampTypeConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ public function castValue($value, Type $type, array $path, array $context): Gene
throw InvalidObjectException::unsupportedType($type);
}

// phpcs:ignore Generic.Files.LineLength
$format = $this->annotationReader->getAnnotations(Format::class, $type->getHolder())->current()->value ?? $context[ContextKey::TIMESTAMP_FORMAT] ?? self::DEFAULT_FORMAT;
$format = $this->annotationReader->getAnnotations(Format::class, $type->getHolder())->current()->value
?? $context[ContextKey::TIMESTAMP_FORMAT]
?? self::DEFAULT_FORMAT;

if (is_string($value)) {
$value = trim($value);
Expand Down
Loading

0 comments on commit 066d740

Please sign in to comment.