Skip to content

Commit

Permalink
refactor: Simplify MapperInterface
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi committed Jan 12, 2024
1 parent a439c07 commit 65bb1f9
Show file tree
Hide file tree
Showing 19 changed files with 374 additions and 215 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
* feat: Constructor arguments
* test: Custom transformer
* refactor: Move `MixedType` to contracts
* refactor: Move standalone `MapperFactory` under MapperFactory namespace
* refactor: Simplify `MapperInterface`
* test: Fix tests due to refactor
* refactor: Move deprecated facade to Facade namespace

## 0.5.3

Expand Down
25 changes: 25 additions & 0 deletions src/Exception/NonSimpleTypeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Exception;

use Rekalogika\Mapper\Util\TypeUtil;
use Symfony\Component\PropertyInfo\Type;

class NonSimpleTypeException extends UnexpectedValueException
{
public function __construct(Type $type)
{
parent::__construct(sprintf('Expected a simple type, got non-simple type "%s".', TypeUtil::getDebugType($type)));
}
}
134 changes: 134 additions & 0 deletions src/Facade/AllPurposeMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Facade;

use Rekalogika\Mapper\Contracts\MainTransformerInterface;
use Rekalogika\Mapper\Contracts\MixedType;
use Rekalogika\Mapper\Exception\MapperReturnsUnexpectedValueException;
use Rekalogika\Mapper\Exception\UnexpectedValueException;
use Rekalogika\Mapper\Util\TypeFactory;
use Symfony\Component\PropertyInfo\Type;

final class AllPurposeMapper implements AllPurposeMapperInterface
{
/**
* Informs the key and value type of the member of the target collection.
*/
public const TARGET_KEY_TYPE = 'target_key_type';
public const TARGET_VALUE_TYPE = 'target_value_type';

public function __construct(
private MainTransformerInterface $transformer,
) {
}

public function map(mixed $source, mixed $target, array $context = []): mixed
{
$originalTarget = $target;

if (
is_string($target)
&& (
class_exists($target)
|| interface_exists($target)
|| enum_exists($target)
)
) {
/** @var class-string $target */
$targetClass = $target;
$targetType = TypeFactory::objectOfClass($targetClass);
$target = null;
} elseif (is_object($target)) {
/** @var object $target */
$targetClass = $target::class;
$targetType = TypeFactory::objectOfClass($targetClass);
} else {
$targetClass = null;
$targetType = TypeFactory::fromBuiltIn($target);
$target = null;
}

/** @var ?string */
$contextTargetKeyType = $context[self::TARGET_KEY_TYPE] ?? null;
/** @var ?string */
$contextTargetValueType = $context[self::TARGET_VALUE_TYPE] ?? null;
unset($context[self::TARGET_KEY_TYPE]);
unset($context[self::TARGET_VALUE_TYPE]);

$targetKeyType = null;
$targetValueType = null;

if ($contextTargetKeyType) {
$targetKeyType = TypeFactory::fromString($contextTargetKeyType);
if ($targetKeyType instanceof MixedType) {
$targetKeyType = null;
}
}

if ($contextTargetValueType) {
$targetValueType = TypeFactory::fromString($contextTargetValueType);
if ($targetValueType instanceof MixedType) {
$targetValueType = null;
}
}

if ($targetKeyType !== null || $targetValueType !== null) {
$targetType = new Type(
builtinType: $targetType->getBuiltinType(),
nullable: $targetType->isNullable(),
class: $targetType->getClassName(),
collection: true,
collectionKeyType: $targetKeyType,
collectionValueType: $targetValueType,
);
}

/** @var mixed */
$target = $this->transformer->transform(
source: $source,
target: $target,
targetType: $targetType,
context: $context
);

if (is_object($target) && is_string($targetClass)) {
if (!is_a($target, $targetClass)) {
throw new UnexpectedValueException(sprintf('The transformer did not return the variable of expected class, expecting "%s", returned "%s".', $targetClass, get_debug_type($target)));
}
return $target;
}

if ($originalTarget === 'string' && is_string($target)) {
return $target;
}

if ($originalTarget === 'int' && is_int($target)) {
return $target;
}

if ($originalTarget === 'float' && is_float($target)) {
return $target;
}

if ($originalTarget === 'bool' && is_bool($target)) {
return $target;
}

if ($originalTarget === 'array' && is_array($target)) {
return $target;
}

throw new MapperReturnsUnexpectedValueException($targetType, $target);
}
}
34 changes: 34 additions & 0 deletions src/Facade/AllPurposeMapperInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Facade;

use Rekalogika\Mapper\Exception\CircularReferenceException;
use Rekalogika\Mapper\Exception\ExceptionInterface;
use Rekalogika\Mapper\Exception\InvalidArgumentException;
use Rekalogika\Mapper\Exception\LogicException;

interface AllPurposeMapperInterface
{
/**
* @template T of object
* @param class-string<T>|T|"int"|"string"|"float"|"bool"|"array" $target
* @param array<string,mixed> $context
* @return ($target is class-string<T>|T ? T : ($target is "int" ? int : ($target is "string" ? string : ($target is "float" ? float : ($target is "bool" ? bool : ($target is "array" ? array<array-key,mixed> : mixed ))))))
* @throws InvalidArgumentException
* @throws CircularReferenceException
* @throws LogicException
* @throws ExceptionInterface
*/
public function map(mixed $source, mixed $target, array $context = []): mixed;
}
109 changes: 22 additions & 87 deletions src/Mapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,84 +14,38 @@
namespace Rekalogika\Mapper;

use Rekalogika\Mapper\Contracts\MainTransformerInterface;
use Rekalogika\Mapper\Contracts\MixedType;
use Rekalogika\Mapper\Exception\MapperReturnsUnexpectedValueException;
use Rekalogika\Mapper\Exception\UnexpectedValueException;
use Rekalogika\Mapper\Util\TypeFactory;
use Symfony\Component\PropertyInfo\Type;

final class Mapper implements MapperInterface
{
/**
* Informs the key and value type of the member of the target collection.
*/
public const TARGET_KEY_TYPE = 'target_key_type';
public const TARGET_VALUE_TYPE = 'target_value_type';

public function __construct(
private MainTransformerInterface $transformer,
) {
}

public function map(mixed $source, mixed $target, array $context = []): mixed
/**
* @template T of object
* @param class-string<T>|T $target
* @param array<string,mixed> $context
* @return T
*/
public function map(mixed $source, object|string $target, array $context = []): object
{
$originalTarget = $target;

if (
is_string($target)
&& (
class_exists($target)
|| interface_exists($target)
|| enum_exists($target)
)
) {
/** @var class-string $target */
if (is_string($target)) {
$targetClass = $target;
if (
!class_exists($targetClass)
&& !\interface_exists($targetClass)
) {
throw new UnexpectedValueException(sprintf('The target class "%s" does not exist.', $targetClass));
}
$targetType = TypeFactory::objectOfClass($targetClass);
$target = null;
} elseif (is_object($target)) {
/** @var object $target */
$targetClass = $target::class;
$targetType = TypeFactory::objectOfClass($targetClass);
} else {
$targetClass = null;
$targetType = TypeFactory::fromBuiltIn($target);
$target = null;
}

/** @var ?string */
$contextTargetKeyType = $context[self::TARGET_KEY_TYPE] ?? null;
/** @var ?string */
$contextTargetValueType = $context[self::TARGET_VALUE_TYPE] ?? null;
unset($context[self::TARGET_KEY_TYPE]);
unset($context[self::TARGET_VALUE_TYPE]);

$targetKeyType = null;
$targetValueType = null;

if ($contextTargetKeyType) {
$targetKeyType = TypeFactory::fromString($contextTargetKeyType);
if ($targetKeyType instanceof MixedType) {
$targetKeyType = null;
}
}

if ($contextTargetValueType) {
$targetValueType = TypeFactory::fromString($contextTargetValueType);
if ($targetValueType instanceof MixedType) {
$targetValueType = null;
}
}

if ($targetKeyType !== null || $targetValueType !== null) {
$targetType = new Type(
builtinType: $targetType->getBuiltinType(),
nullable: $targetType->isNullable(),
class: $targetType->getClassName(),
collection: true,
collectionKeyType: $targetKeyType,
collectionValueType: $targetValueType,
);
/** @var T $target */
$targetClass = get_class($target);
$targetType = TypeFactory::objectOfClass($targetClass);
}

/** @var mixed */
Expand All @@ -102,33 +56,14 @@ class: $targetType->getClassName(),
context: $context
);

if (is_object($target) && is_string($targetClass)) {
if (!is_a($target, $targetClass)) {
throw new UnexpectedValueException(sprintf('The transformer did not return the variable of expected class, expecting "%s", returned "%s".', $targetClass, get_debug_type($target)));
}
return $target;
}

if ($originalTarget === 'string' && is_string($target)) {
return $target;
}

if ($originalTarget === 'int' && is_int($target)) {
return $target;
}

if ($originalTarget === 'float' && is_float($target)) {
return $target;
}

if ($originalTarget === 'bool' && is_bool($target)) {
return $target;
if ($target === null) {
throw new UnexpectedValueException(sprintf('The mapper returned null, expecting "%s".', $targetClass));
}

if ($originalTarget === 'array' && is_array($target)) {
return $target;
if (!is_object($target) || !is_a($target, $targetClass)) {
throw new UnexpectedValueException(sprintf('The mapper did not return the variable of expected class, expecting "%s", returned "%s".', $targetClass, get_debug_type($target)));
}

throw new MapperReturnsUnexpectedValueException($targetType, $target);
return $target;
}
}
15 changes: 3 additions & 12 deletions src/MapperInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,13 @@

namespace Rekalogika\Mapper;

use Rekalogika\Mapper\Exception\CircularReferenceException;
use Rekalogika\Mapper\Exception\ExceptionInterface;
use Rekalogika\Mapper\Exception\InvalidArgumentException;
use Rekalogika\Mapper\Exception\LogicException;

interface MapperInterface
{
/**
* @template T of object
* @param class-string<T>|T|"int"|"string"|"float"|"bool"|"array" $target
* @param class-string<T>|T $target
* @param array<string,mixed> $context
* @return ($target is class-string<T>|T ? T : ($target is "int" ? int : ($target is "string" ? string : ($target is "float" ? float : ($target is "bool" ? bool : ($target is "array" ? array<array-key,mixed> : mixed ))))))
* @throws InvalidArgumentException
* @throws CircularReferenceException
* @throws LogicException
* @throws ExceptionInterface
* @return T
*/
public function map(mixed $source, mixed $target, array $context = []): mixed;
public function map(mixed $source, object|string $target, array $context = []): mixed;
}
Loading

0 comments on commit 65bb1f9

Please sign in to comment.