Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/mapper/src/Mappers/ObjectToArrayMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ private function resolvePropertyValue(PropertyReflector $property, object $objec
continue;
}

$propertyValue[$key] = map($value)->toArray();
$propertyValue[$key] = map($value)
->in($this->context)
->toArray();
}

return $propertyValue;
Expand All @@ -79,6 +81,12 @@ private function resolvePropertyValue(PropertyReflector $property, object $objec
return $serializer->serialize($propertyValue);
}

if ($propertyValue !== null && is_object($propertyValue)) {
return map($propertyValue)
->in($this->context)
->toArray();
}

return $propertyValue;
}

Expand Down
17 changes: 17 additions & 0 deletions tests/Integration/Mapper/Fixtures/NestedObjectWithDate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Tests\Tempest\Integration\Mapper\Fixtures;

use Tempest\DateTime\DateTime;
use Tempest\DateTime\FormatPattern;
use Tempest\Validation\Rules\HasDateTimeFormat;

final readonly class NestedObjectWithDate
{
public function __construct(
#[HasDateTimeFormat(FormatPattern::ISO8601)]
public DateTime $createdAt,
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Tests\Tempest\Integration\Mapper\Fixtures;

use Tempest\DateTime\DateTime;
use Tempest\DateTime\FormatPattern;
use Tempest\Validation\Rules\HasDateTimeFormat;

final readonly class ObjectWithNestedObjectAndDate
{
public function __construct(
#[HasDateTimeFormat(FormatPattern::ISO8601)]
public DateTime $createdAt,
public NestedObjectWithDate $child,
/** @var \Tests\Tempest\Integration\Mapper\Fixtures\NestedObjectWithDate[] */
public array $children,
) {}
}
115 changes: 115 additions & 0 deletions tests/Integration/Mapper/Mappers/ObjectToArrayMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@

namespace Tests\Tempest\Integration\Mapper\Mappers;

use PHPUnit\Framework\Attributes\RequiresPhpExtension;
use PHPUnit\Framework\Attributes\Test;
use RuntimeException;
use Tempest\DateTime\DateTime;
use Tempest\Mapper\Mapper;
use Tempest\Mapper\MapperConfig;
use Tempest\Support\Json\Exception\JsonCouldNotBeEncoded;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
use Tests\Tempest\Integration\Mapper\Fixtures\NestedObjectWithDate;
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectA;
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithJsonSerialize;
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithNestedObjectAndDate;
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithNullableProperties;
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithScalarValues;
use Tests\Tempest\Integration\Mapper\Fixtures\ParentObject;

use function Tempest\Mapper\map;

Expand Down Expand Up @@ -69,4 +78,110 @@
$array,
);
}

#[Test]
public function object_with_single_nested_object_to_array(): void
{
$date = DateTime::parse('2026-08-19T12:34:56+00:00');

$array = map(new ObjectWithNestedObjectAndDate(
createdAt: $date,
child: new NestedObjectWithDate($date),
children: [new NestedObjectWithDate($date)],
))->toArray();

$this->assertSame(
[
'createdAt' => '2026-08-19T12:34:56.000Z',
'child' => [
'createdAt' => '2026-08-19T12:34:56.000Z',
],
'children' => [
['createdAt' => '2026-08-19T12:34:56.000Z'],
],
],
$array,
);
}

#[Test]
#[RequiresPhpExtension('pcntl')]
public function cyclic_nested_objects_fail_instead_of_hanging(): void
{
$parent = map([
'name' => 'parent',
'child' => ['name' => 'child'],
])->to(ParentObject::class);
$this->assertInstanceOf(ParentObject::class, $parent);

pcntl_async_signals(true);
pcntl_signal(SIGALRM, static function (): never {
throw new RuntimeException('Serialization did not terminate');
});
pcntl_alarm(2);

try {
$this->expectException(JsonCouldNotBeEncoded::class);

map($parent)->toJson();
} finally {
pcntl_alarm(0);
pcntl_signal(SIGALRM, SIG_DFL);
}
}

#[Test]
public function nested_objects_do_not_resolve_unused_mappers(): void
{
map(new ObjectA('a', 'b'))->toArray();

$this->container
->get(MapperConfig::class)
->addMapper(MapperResolutionProbe::class);

MapperResolutionProbe::$constructions = 0;

map(new ObjectWithMapperResolutionChildren([
new MapperResolutionChild('a'),
new MapperResolutionChild('b'),
new MapperResolutionChild('c'),
]))->toArray();

$this->assertSame(0, MapperResolutionProbe::$constructions);

Check failure on line 150 in tests/Integration/Mapper/Mappers/ObjectToArrayMapperTest.php

View workflow job for this annotation

GitHub Actions / Run tests: PHP 8.5 - sqlite - prefer-stable - ubuntu-latest

Failed asserting that 3 is identical to 0.

Check failure on line 150 in tests/Integration/Mapper/Mappers/ObjectToArrayMapperTest.php

View workflow job for this annotation

GitHub Actions / Run tests: PHP 8.5 - postgres - prefer-stable - ubuntu-latest

Failed asserting that 3 is identical to 0.

Check failure on line 150 in tests/Integration/Mapper/Mappers/ObjectToArrayMapperTest.php

View workflow job for this annotation

GitHub Actions / Run tests: PHP 8.5 - sqlite - prefer-lowest - ubuntu-latest

Failed asserting that 3 is identical to 0.

Check failure on line 150 in tests/Integration/Mapper/Mappers/ObjectToArrayMapperTest.php

View workflow job for this annotation

GitHub Actions / Run tests: PHP 8.5 - mysql - prefer-stable - ubuntu-latest

Failed asserting that 3 is identical to 0.

Check failure on line 150 in tests/Integration/Mapper/Mappers/ObjectToArrayMapperTest.php

View workflow job for this annotation

GitHub Actions / Run tests: PHP 8.5 - sqlite - prefer-stable - windows-latest

Failed asserting that 3 is identical to 0.
}
}

final readonly class ObjectWithMapperResolutionChildren
{
public function __construct(
/** @var \Tests\Tempest\Integration\Mapper\Mappers\MapperResolutionChild[] */
public array $children,
) {}
}

final readonly class MapperResolutionChild
{
public function __construct(
public string $name,
) {}
}

final class MapperResolutionProbe implements Mapper
{
public static int $constructions = 0;

public function __construct()
{
self::$constructions++;
}

public function canMap(mixed $from, mixed $to): bool
{
return false;
}

public function map(mixed $from, mixed $to): mixed
{
return $from;
}
}
28 changes: 28 additions & 0 deletions tests/Integration/Mapper/Mappers/ObjectToJsonMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
namespace Tests\Tempest\Integration\Mapper\Mappers;

use PHPUnit\Framework\Attributes\Test;
use Tempest\DateTime\DateTime;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
use Tests\Tempest\Integration\Mapper\Fixtures\NestedObjectWithDate;
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectA;
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithNestedObjectAndDate;
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithScalarValues;

use function Tempest\Mapper\map;
Expand Down Expand Up @@ -42,4 +45,29 @@ public function object_with_scalar_values_to_json(): void
json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR),
);
}

#[Test]
public function object_with_single_nested_object_to_json(): void
{
$date = DateTime::parse('2026-08-19T12:34:56+00:00');

$json = map(new ObjectWithNestedObjectAndDate(
createdAt: $date,
child: new NestedObjectWithDate($date),
children: [new NestedObjectWithDate($date)],
))->toJson();

$this->assertSame(
[
'createdAt' => '2026-08-19T12:34:56.000Z',
'child' => [
'createdAt' => '2026-08-19T12:34:56.000Z',
],
'children' => [
['createdAt' => '2026-08-19T12:34:56.000Z'],
],
],
json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@
namespace Tests\Tempest\Integration\Mapper\Serializers;

use PHPUnit\Framework\Attributes\Test;
use Tempest\Mapper\DynamicSerializer;
use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized;
use Tempest\Mapper\MappingContext;
use Tempest\Mapper\Serializer;
use Tempest\Mapper\SerializerFactory;
use Tempest\Mapper\Serializers\ArrayOfObjectsSerializer;
use Tempest\Mapper\Serializers\EnumSerializer;
use Tempest\Reflection\PropertyReflector;
use Tempest\Reflection\TypeReflector;
use Tempest\Support\Priority;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithSerializerProperties;

Expand Down Expand Up @@ -51,4 +58,52 @@

new EnumSerializer()->serialize('foo');
}

#[Test]
public function uses_mapping_context_for_nested_objects(): void
{
$context = MappingContext::from('api');

$this->container
->get(SerializerFactory::class)
->addSerializer(
ContextStringSerializer::class,
priority: Priority::HIGHEST,
context: $context,
);

$serializer = $this->container->get(
ArrayOfObjectsSerializer::class,
context: $context,
);

$this->assertSame(

Check failure on line 80 in tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php

View workflow job for this annotation

GitHub Actions / Run tests: PHP 8.5 - sqlite - prefer-stable - ubuntu-latest

Failed asserting that two arrays are identical.

Check failure on line 80 in tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php

View workflow job for this annotation

GitHub Actions / Run tests: PHP 8.5 - postgres - prefer-stable - ubuntu-latest

Failed asserting that two arrays are identical.

Check failure on line 80 in tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php

View workflow job for this annotation

GitHub Actions / Run tests: PHP 8.5 - sqlite - prefer-lowest - ubuntu-latest

Failed asserting that two arrays are identical.

Check failure on line 80 in tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php

View workflow job for this annotation

GitHub Actions / Run tests: PHP 8.5 - mysql - prefer-stable - ubuntu-latest

Failed asserting that two arrays are identical.

Check failure on line 80 in tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php

View workflow job for this annotation

GitHub Actions / Run tests: PHP 8.5 - sqlite - prefer-stable - windows-latest

Failed asserting that two arrays are identical.
[['name' => 'api:a']],
$serializer->serialize([new ContextObject('a')]),
);
}
}

final readonly class ContextObject
{
public function __construct(
public string $name,
) {}
}

final class ContextStringSerializer implements Serializer, DynamicSerializer
{
public static function accepts(PropertyReflector|TypeReflector $input): bool
{
$type = $input instanceof PropertyReflector
? $input->getType()
: $input;

return $type->getName() === 'string';
}

public function serialize(mixed $input): string
{
return 'api:' . $input;
}
}
Loading