Skip to content

Commit

Permalink
Use result object instead of exception for type cast control flow (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik committed May 25, 2023
1 parent 5acbc12 commit e392eb6
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 63 deletions.
18 changes: 9 additions & 9 deletions src/Hydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ private function getConstructorArguments(string $class, array $sourceData, array
}

if ($resolved) {
try {
$constructorArguments[$parameterName] = $this->typeCaster->cast(
$resolvedValue,
$parameter->getType()
);
} catch (SkipTypeCastException) {
$result = $this->typeCaster->cast(
$resolvedValue,
$parameter->getType()
);
if ($result->isCasted()) {
$constructorArguments[$parameterName] = $result->getValue();
}
}
}
Expand Down Expand Up @@ -162,9 +162,9 @@ private function getHydrateData(
}

if ($resolved) {
try {
$hydrateData[$propertyName] = $this->typeCaster->cast($resolvedValue, $property->getType());
} catch (SkipTypeCastException) {
$result = $this->typeCaster->cast($resolvedValue, $property->getType());
if ($result->isCasted()) {
$hydrateData[$propertyName] = $result->getValue();
}
}
}
Expand Down
8 changes: 3 additions & 5 deletions src/ParameterAttributesHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,9 @@ public function handle(
if ($this->typeCaster === null) {
return $resolvedValue;
}
try {
return $this->typeCaster->cast($resolvedValue, $parameter->getType());
} catch (SkipTypeCastException) {
return $resolvedValue;
}

$result = $this->typeCaster->cast($resolvedValue, $parameter->getType());
return $result->isCasted() ? $result->getValue() : $resolvedValue;
}

throw new NotResolvedException();
Expand Down
11 changes: 0 additions & 11 deletions src/SkipTypeCastException.php

This file was deleted.

34 changes: 34 additions & 0 deletions src/TypeCastResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator;

final class TypeCastResult
{
private function __construct(
private bool $isCasted,
private mixed $value = null,
) {
}

public static function success(mixed $value): self
{
return new self(true, $value);
}

public static function skip(): self
{
return new self(false);
}

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

public function getValue(): mixed
{
return $this->value;
}
}
12 changes: 6 additions & 6 deletions src/TypeCaster/CompositeTypeCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
namespace Yiisoft\Hydrator\TypeCaster;

use ReflectionType;
use Yiisoft\Hydrator\SkipTypeCastException;
use Yiisoft\Hydrator\TypeCasterInterface;
use Yiisoft\Hydrator\TypeCastResult;

final class CompositeTypeCaster implements TypeCasterInterface
{
Expand All @@ -21,15 +21,15 @@ public function __construct(
$this->typeCasters = $typeCasters;
}

public function cast(mixed $value, ?ReflectionType $type): mixed
public function cast(mixed $value, ?ReflectionType $type): TypeCastResult
{
foreach ($this->typeCasters as $typeCaster) {
try {
return $typeCaster->cast($value, $type);
} catch (SkipTypeCastException) {
$result = $typeCaster->cast($value, $type);
if ($result->isCasted()) {
return $result;
}
}

throw new SkipTypeCastException();
return TypeCastResult::skip();
}
}
5 changes: 3 additions & 2 deletions src/TypeCaster/NoTypeCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@

use ReflectionType;
use Yiisoft\Hydrator\TypeCasterInterface;
use Yiisoft\Hydrator\TypeCastResult;

final class NoTypeCaster implements TypeCasterInterface
{
public function cast(mixed $value, ?ReflectionType $type): mixed
public function cast(mixed $value, ?ReflectionType $type): TypeCastResult
{
return $value;
return TypeCastResult::success($value);
}
}
40 changes: 21 additions & 19 deletions src/TypeCaster/SimpleTypeCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
use ReflectionUnionType;
use Stringable;
use Yiisoft\Hydrator\HydratorInterface;
use Yiisoft\Hydrator\SkipTypeCastException;
use Yiisoft\Hydrator\TypeCasterInterface;
use Yiisoft\Hydrator\TypeCastResult;
use Yiisoft\Strings\NumericHelper;

use function is_array;
Expand All @@ -32,7 +32,7 @@ public function withHydrator(HydratorInterface $hydrator): self
return $new;
}

public function cast(mixed $value, ?ReflectionType $type): mixed
public function cast(mixed $value, ?ReflectionType $type): TypeCastResult
{
if ($type instanceof ReflectionNamedType) {
$types = [$type];
Expand All @@ -42,44 +42,44 @@ public function cast(mixed $value, ?ReflectionType $type): mixed
static fn(mixed $type) => $type instanceof ReflectionNamedType,
);
} elseif ($type === null) {
return $value;
return TypeCastResult::success($value);
} else {
throw new SkipTypeCastException();
return TypeCastResult::skip();
}

foreach ($types as $t) {
if (null === $value && $t->allowsNull()) {
return null;
return TypeCastResult::success(null);
}
if ($t->isBuiltin()) {
switch ($t->getName()) {
case 'string':
if (is_string($value)) {
return $value;
return TypeCastResult::success($value);
}
break;

case 'int':
if (is_int($value)) {
return $value;
return TypeCastResult::success($value);
}
break;

case 'float':
if (is_float($value)) {
return $value;
return TypeCastResult::success($value);
}
break;

case 'bool':
if (is_bool($value)) {
return $value;
return TypeCastResult::success($value);
}
break;

case 'array':
if (is_array($value)) {
return $value;
return TypeCastResult::success($value);
}
break;
}
Expand All @@ -91,31 +91,31 @@ public function cast(mixed $value, ?ReflectionType $type): mixed
switch ($t->getName()) {
case 'string':
if (is_scalar($value) || null === $value || $value instanceof Stringable) {
return (string) $value;
return TypeCastResult::success((string) $value);
}
break;

case 'int':
if (is_bool($value) || is_float($value) || null === $value) {
return (int) $value;
return TypeCastResult::success((int) $value);
}
if ($value instanceof Stringable || is_string($value)) {
return (int) NumericHelper::normalize((string) $value);
return TypeCastResult::success((int) NumericHelper::normalize((string) $value));
}
break;

case 'float':
if (is_int($value) || is_bool($value) || null === $value) {
return (float) $value;
return TypeCastResult::success((float) $value);
}
if ($value instanceof Stringable || is_string($value)) {
return (float) NumericHelper::normalize((string) $value);
return TypeCastResult::success((float) NumericHelper::normalize((string) $value));
}
break;

case 'bool':
if (is_scalar($value) || null === $value || is_array($value) || is_object($value)) {
return (bool) $value;
return TypeCastResult::success((bool) $value);
}
break;
}
Expand All @@ -125,16 +125,18 @@ public function cast(mixed $value, ?ReflectionType $type): mixed
$class = $t->getName();
if (is_object($value)) {
if (is_a($value, $class)) {
return $value;
return TypeCastResult::success($value);
}
} elseif (is_array($value) && $this->hydrator !== null) {
$reflection = new ReflectionClass($class);
if ($reflection->isInstantiable()) {
return $this->hydrator->create($class, $value);
return TypeCastResult::success(
$this->hydrator->create($class, $value)
);
}
}
}

throw new SkipTypeCastException();
return TypeCastResult::skip();
}
}
5 changes: 1 addition & 4 deletions src/TypeCasterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,5 @@

interface TypeCasterInterface
{
/**
* @throws SkipTypeCastException
*/
public function cast(mixed $value, ?ReflectionType $type): mixed;
public function cast(mixed $value, ?ReflectionType $type): TypeCastResult;
}
6 changes: 3 additions & 3 deletions tests/Support/SkipTypeCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
namespace Yiisoft\Hydrator\Tests\Support;

use ReflectionType;
use Yiisoft\Hydrator\SkipTypeCastException;
use Yiisoft\Hydrator\TypeCasterInterface;
use Yiisoft\Hydrator\TypeCastResult;

final class SkipTypeCaster implements TypeCasterInterface
{
public function cast(mixed $value, ?ReflectionType $type): mixed
public function cast(mixed $value, ?ReflectionType $type): TypeCastResult
{
throw new SkipTypeCastException();
return TypeCastResult::skip();
}
}
8 changes: 4 additions & 4 deletions tests/Support/String42TypeCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@

use ReflectionNamedType;
use ReflectionType;
use Yiisoft\Hydrator\SkipTypeCastException;
use Yiisoft\Hydrator\TypeCasterInterface;
use Yiisoft\Hydrator\TypeCastResult;

final class String42TypeCaster implements TypeCasterInterface
{
public function cast(mixed $value, ?ReflectionType $type): mixed
public function cast(mixed $value, ?ReflectionType $type): TypeCastResult
{
if ($type instanceof ReflectionNamedType
&& $type->isBuiltin()
&& $type->getName() === 'string'
) {
return '42';
return TypeCastResult::success('42');
}

throw new SkipTypeCastException();
return TypeCastResult::skip();
}
}

0 comments on commit e392eb6

Please sign in to comment.