Skip to content

Commit

Permalink
fix: Remove iterating object key because not supported in PHP.
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi committed Jan 15, 2024
1 parent 3411ad2 commit 0f15c7e
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 103 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
type.
* feat(`TraversableToArrayAccessTransformer`): Now supports `ArrayCollection` & `ArrayIterator`.
* test: Assorted tests.
* fix: Remove iterating object key because not supported in PHP.
* test: Assorted tests.

## 0.5.10

Expand Down
48 changes: 7 additions & 41 deletions src/Transformer/TraversableToArrayAccessTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
use Rekalogika\Mapper\Transformer\Contracts\TypeMapping;
use Rekalogika\Mapper\Transformer\Exception\ClassNotInstantiableException;
use Rekalogika\Mapper\Transformer\Exception\InvalidTypeInArgumentException;
use Rekalogika\Mapper\Transformer\Exception\MissingMemberKeyTypeException;
use Rekalogika\Mapper\Util\TypeCheck;
use Rekalogika\Mapper\Util\TypeFactory;
use Symfony\Component\PropertyInfo\Type;
Expand Down Expand Up @@ -68,7 +67,6 @@ public function transform(
// Prepare variables for the output loop

$targetMemberKeyType = $targetType->getCollectionKeyTypes();
$targetMemberKeyTypeIsMissing = count($targetMemberKeyType) === 0;
$targetMemberKeyTypeIsInt = count($targetMemberKeyType) === 1
&& TypeCheck::isInt($targetMemberKeyType[0]);
$targetMemberValueType = $targetType->getCollectionValueTypes();
Expand All @@ -77,47 +75,15 @@ public function transform(

/** @var mixed $sourceMemberValue */
foreach ($source as $sourceMemberKey => $sourceMemberValue) {
/** @var mixed $sourceMemberKey */
// if target has int key type but the source has string key type,
// we discard the source key & use null (i.e. $target[] = $value)

if (is_string($sourceMemberKey) || is_int($sourceMemberKey)) {
// if the key is a simple type: int|string

if ($targetMemberKeyTypeIsInt && is_string($sourceMemberKey)) {
// if target has int key type but the source has string key type,
// we discard the source key & use null (i.e. $target[] = $value)

$targetMemberKey = null;
$path = sprintf('[%d]', $i);
} else {
$targetMemberKey = $sourceMemberKey;
$path = sprintf('[%s]', $sourceMemberKey);
}
if ($targetMemberKeyTypeIsInt && is_string($sourceMemberKey)) {
$targetMemberKey = null;
$path = sprintf('[%d]', $i);
} else {
// If the type of the key is a complex type (not int or string).
// i.e. an ArrayObject can have an object as its key.

// Refuse to continue if the target key type is not provided

if ($targetMemberKeyTypeIsMissing) {
throw new MissingMemberKeyTypeException($sourceType, $targetType, context: $context);
}

// If provided, we transform the source key to the key type of
// the target

/** @var mixed */
$targetMemberKey = $this->getMainTransformer()->transform(
source: $sourceMemberKey,
target: null,
targetTypes: $targetMemberKeyType,
context: $context,
);

if ($targetMemberKey instanceof \Stringable) {
$path = sprintf('[%s]', $targetMemberKey);
} else {
$path = sprintf('[%s]', get_debug_type($targetMemberKey));
}
$targetMemberKey = $sourceMemberKey;
$path = sprintf('[%s]', $sourceMemberKey);
}

// Get the existing member value from the target
Expand Down
62 changes: 8 additions & 54 deletions src/Transformer/TraversableToTraversableTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
use Rekalogika\Mapper\Transformer\Contracts\MainTransformerAwareTrait;
use Rekalogika\Mapper\Transformer\Contracts\TransformerInterface;
use Rekalogika\Mapper\Transformer\Contracts\TypeMapping;
use Rekalogika\Mapper\Transformer\Exception\MissingMemberKeyTypeException;
use Rekalogika\Mapper\Transformer\Exception\MissingMemberValueTypeException;
use Rekalogika\Mapper\Transformer\Model\TraversableCountableWrapper;
use Rekalogika\Mapper\Util\TypeCheck;
use Rekalogika\Mapper\Util\TypeFactory;
Expand Down Expand Up @@ -57,76 +55,32 @@ public function transform(
// We can't work if the target type doesn't contain the information
// about the type of its member objects

$targetMemberValueType = $targetType->getCollectionValueTypes();

if (count($targetMemberValueType) === 0) {
throw new MissingMemberValueTypeException($sourceType, $targetType, context: $context);
}

// Prepare variables for the output loop

$targetMemberKeyType = $targetType->getCollectionKeyTypes();
$targetMemberKeyTypeIsMissing = count($targetMemberKeyType) === 0;
$targetMemberKeyTypeIsInt = count($targetMemberKeyType) === 1
&& TypeCheck::isInt($targetMemberKeyType[0]);
$targetMemberValueType = $targetType->getCollectionValueTypes();

// create generator

$target = (function () use (
$source,
$targetMemberKeyTypeIsInt,
$targetMemberKeyTypeIsMissing,
$sourceType,
$targetType,
$targetMemberKeyType,
$targetMemberValueType,
$context
): \Traversable {
$i = 0;

/** @var mixed $sourcePropertyValue */
foreach ($source as $sourcePropertyKey => $sourcePropertyValue) {
/** @var mixed $sourcePropertyKey */

if (is_string($sourcePropertyKey) || is_int($sourcePropertyKey)) {
// if the key is a simple type: int|string

if ($targetMemberKeyTypeIsInt && is_string($sourcePropertyKey)) {
// if target has int key type but the source has string key type,
// we discard the source key & use null (i.e. $target[] = $value)
// if target has int key type but the source has string key type,
// we discard the source key & use null (i.e. $target[] = $value)

$targetMemberKey = null;
$path = sprintf('[%d]', $i);
} else {
$targetMemberKey = $sourcePropertyKey;
$path = sprintf('[%s]', $sourcePropertyKey);
}
if ($targetMemberKeyTypeIsInt && is_string($sourcePropertyKey)) {
$targetMemberKey = null;
$path = sprintf('[%d]', $i);
} else {
// If the type of the key is a complex type (not int or string).
// i.e. an ArrayObject can have an object as its key.

// Refuse to continue if the target key type is not provided

if ($targetMemberKeyTypeIsMissing) {
throw new MissingMemberKeyTypeException($sourceType, $targetType, context: $context);
}

// If provided, we transform the source key to the key type of
// the target

/** @var mixed */
$targetMemberKey = $this->getMainTransformer()->transform(
source: $sourcePropertyKey,
target: null,
targetTypes: $targetMemberKeyType,
context: $context,
);

if ($targetMemberKey instanceof \Stringable) {
$path = sprintf('[%s]', $targetMemberKey);
} else {
$path = sprintf('[%s]', get_debug_type($targetMemberKey));
}
$targetMemberKey = $sourcePropertyKey;
$path = sprintf('[%s]', $sourcePropertyKey);
}

// now transform the source member value to the type of the target
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@

class ObjectWithArrayAccessPropertyWithoutTypeHintDto
{
/**
* @var \ArrayAccess<array-key,mixed>
*/
// @phpstan-ignore-next-line
public \ArrayAccess $property;

public function __construct()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@

class ObjectWithArrayPropertyWithoutTypeHintDto
{
/**
* @var array<array-key,mixed>
*/
// @phpstan-ignore-next-line
public array $property = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?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\Tests\Fixtures\ArrayLikeDto;

class ObjectWithTraversablePropertyWithoutTypeHintDto
{
// @phpstan-ignore-next-line
public ?\Traversable $property = null;
}
8 changes: 6 additions & 2 deletions tests/IntegrationTest/TraversableToArrayAccessMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@

class TraversableToArrayAccessMappingTest extends AbstractIntegrationTest
{
public function testFailedArrayToArray(): void
//
// without typehint
//

public function testArrayToArrayWithoutTypehint(): void
{
$source = new ObjectWithArrayProperty();
$result = $this->mapper->map($source, ObjectWithArrayPropertyWithoutTypeHintDto::class);
Expand All @@ -44,7 +48,7 @@ public function testFailedArrayToArray(): void
$this->assertInstanceOf(ObjectWithScalarProperties::class, $result->property[2]);
}

public function testFailedTraversableToArrayAccess(): void
public function testTraversableToArrayAccessWithoutTypehint(): void
{
$source = new ObjectWithTraversableProperties();
$result = $this->mapper->map($source, ObjectWithArrayPropertyWithoutTypeHintDto::class);
Expand Down
24 changes: 24 additions & 0 deletions tests/IntegrationTest/TraversableToTraversableMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithLazyDoctrineCollectionWithPresetCountableProperty;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithTraversableProperties;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithTraversablePropertyDto;
use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithTraversablePropertyWithoutTypeHintDto;
use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarProperties;
use Rekalogika\Mapper\Tests\Fixtures\ScalarDto\ObjectWithScalarPropertiesDto;
use Rekalogika\Mapper\Transformer\Model\TraversableCountableWrapper;

Expand Down Expand Up @@ -97,4 +99,26 @@ public function testExtraLazy(): void
$this->expectException(\LogicException::class);
foreach ($result->property as $item);
}

//
// without type hint
//

public function testArrayToTraversableWithoutTypehint(): void
{
$source = new ObjectWithArrayProperty();
$result = $this->mapper->map($source, ObjectWithTraversablePropertyWithoutTypeHintDto::class);

$this->assertInstanceOf(\Traversable::class, $result->property);
$this->assertInstanceOf(\Countable::class, $result->property);

foreach ($result->property as $item) {
$this->assertInstanceOf(ObjectWithScalarProperties::class, $item);

$this->assertEquals(1, $item->a);
$this->assertEquals("string", $item->b);
$this->assertEquals(true, $item->c);
$this->assertEquals(1.1, $item->d);
}
}
}

0 comments on commit 0f15c7e

Please sign in to comment.