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
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ resolving steps using [code generation](#maximizing-performance).
- [**Creating your own property casters**](#creating-your-own-property-casters)
- [**Static constructors**](#static-constructors)
- [**Key formatters**](#key-formatters)
- [**Serializing maps as objects**](#serializing-maps-as-objects)
- [**Maximizing performance**](#maximizing-performance)

## Design goals
Expand Down Expand Up @@ -598,6 +599,59 @@ $payload = $mapper->serializeObject(new Shout('Hello, World!');
$payload['what'] === 'HELLO, WORLD!';
```

### Serializing maps as objects
By default, associative arrays are serialized as arrays in the payload. Since associative arrays represent
unstructured key-value maps that are typically treated as objects in many data formats, you may want to serialize
them as objects for better cross-platform compatibility.

You can configure the mapper to serialize associative arrays as objects by enabling the `serializeMapsAsObjects` option:

```php
use EventSauce\ObjectHydrator\DefinitionProvider;
use EventSauce\ObjectHydrator\ObjectMapperUsingReflection;

$mapper = new ObjectMapperUsingReflection(
new DefinitionProvider(serializeMapsAsObjects: true),
);
```

Maps are serialized as objects based on their doc-comment type hints. Arrays with `array<string, T>` type hints
are treated as maps and serialized as objects.

```php
class ExampleCommand
{
/**
* @param array<string, mixed> $changedFields
*/
public function __construct(
public readonly array $changedFields,
) {}

/**
* @return array<string, string>
*/
public function metadata(): array
{
return [
'source' => 'api',
];
}
}

$command = new ExampleCommand(['email' => '[email protected]', 'name' => 'John']);

$payload = $mapper->serializeObject($command);
```

Serialized payload:
```
[
'changed_fields' => {"email": "[email protected]", "name": "John"},
'metadata' => {"source": "api"}
]
```

## Symmetrical conversion

If configured consistently, hydration and serialization can be used to translate an object to raw data
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"license": "MIT",
"require": {
"php": "^8.0",
"ext-fileinfo": "*"
"ext-fileinfo": "*",
"symfony/polyfill-php81": "^1.3"
},
"require-dev": {
"phpunit/phpunit": "^9.5.11",
Expand Down
161 changes: 83 additions & 78 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/ConcreteType.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
*/
final class ConcreteType
{
public bool $associative = false;

public function __construct(public string $name, public bool $isBuiltIn)
{
}
Expand Down
5 changes: 3 additions & 2 deletions src/DefinitionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ private function stringifyConstructor(ReflectionMethod $constructor): string
public function provideSerializationDefinition(string $className): ClassSerializationDefinition
{
$reflection = new ReflectionClass($className);
$constructor = $this->constructorResolver->resolveConstructor($reflection);
$objectSettings = $this->resolveObjectSettings($reflection);
$classAttributes = $reflection->getAttributes();
$properties = [];
Expand Down Expand Up @@ -173,7 +174,7 @@ public function provideSerializationDefinition(string $className): ClassSerializ
PropertySerializationDefinition::TYPE_METHOD,
$methodName,
$this->resolveSerializers($returnType, $attributes),
PropertyType::fromReflectionType($returnType),
$this->propertyTypeResolver->typeFromMethod($method),
$returnType->allowsNull(),
$this->resolveKeys($key, $attributes),
$typeSpecifier?->key,
Expand Down Expand Up @@ -204,7 +205,7 @@ public function provideSerializationDefinition(string $className): ClassSerializ
PropertySerializationDefinition::TYPE_PROPERTY,
$property->getName(),
$serializers,
PropertyType::fromReflectionType($propertyType),
$this->propertyTypeResolver->typeFromProperty($property, $constructor),
$propertyType->allowsNull(),
$this->resolveKeys($key, $attributes),
$typeSpecifier?->key,
Expand Down
22 changes: 22 additions & 0 deletions src/Fixtures/ClassThatHasMultipleCastersOnMapProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace EventSauce\ObjectHydrator\Fixtures;

use EventSauce\ObjectHydrator\PropertyCasters\CastToArrayWithKey;
use EventSauce\ObjectHydrator\PropertyCasters\CastToType;

final class ClassThatHasMultipleCastersOnMapProperty
{
/**
* @param array<string, array<string, string>> $map
*/
public function __construct(
#[CastToType('array')]
#[CastToArrayWithKey('second_level')]
#[CastToArrayWithKey('first_level')]
public array $map,
) {
}
}
16 changes: 16 additions & 0 deletions src/Fixtures/ClassThatSpecifiesArrayWithIntegerKeys.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace EventSauce\ObjectHydrator\Fixtures;

final class ClassThatSpecifiesArrayWithIntegerKeys
{
/**
* @param array<int, string> $arrayWithIntegerKeys
*/
public function __construct(
public array $arrayWithIntegerKeys,
) {
}
}
62 changes: 62 additions & 0 deletions src/Fixtures/ClassThatSpecifiesArraysWithDocComments.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace EventSauce\ObjectHydrator\Fixtures;

use EventSauce\ObjectHydrator\Fixtures\ClassWithCamelCaseProperty as CamelClass;

final class ClassThatSpecifiesArraysWithDocComments
{
/**
* @param array<string, CamelClass> $mapWithObjects
* @param array<string, int> $mapWithScalars
* @param array<string, array<string, string>> $mapWithAssociativeArrays
* @param array<int, string> $listWithTypeHint
*/
public function __construct(
public array $mapWithObjects,
public array $mapWithScalars,
public array $mapWithAssociativeArrays,
public array $listWithoutTypeHint,
public array $listWithTypeHint,
) {
}

/**
* @return array<string, CamelClass>
*/
public function methodMapWithObjects(): array
{
return $this->mapWithObjects;
}

/**
* @return array<string, int>
*/
public function methodMapWithScalars(): array
{
return $this->mapWithScalars;
}

/**
* @return array<string, array<string, string>>
*/
public function methodMapWithAssociativeArrays(): array
{
return $this->mapWithAssociativeArrays;
}

public function methodListWithoutTypeHint(): array
{
return $this->listWithoutTypeHint;
}

/**
* @return array<int, string>
*/
public function methodListWithTypeHint(): array
{
return $this->listWithTypeHint;
}
}
Loading
Loading