From 2257d3f0c96f464f80665a4c1149519d88c96466 Mon Sep 17 00:00:00 2001 From: jerodev Date: Tue, 16 May 2023 18:15:22 +0200 Subject: [PATCH 1/4] Wrap setter if uninitialized fields allowed --- src/MapperConfig.php | 15 +++++++-------- src/Objects/ObjectMapper.php | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/MapperConfig.php b/src/MapperConfig.php index fc36a08..b130ec6 100644 --- a/src/MapperConfig.php +++ b/src/MapperConfig.php @@ -4,35 +4,34 @@ class MapperConfig { + /** + * If disabled, all properties of an object must either be present in the data array or have a default value for + * mapping to succeed. + * If enabled, properties that are not present in the data array will be left uninitialized. + */ + public bool $allowUninitializedFields = true; + /** * This is the directly where generated mappers will be stored. * It is recommended to prune this directory on every deploy to prevent old mappers from being used. * The prefix `{$TMP}` is replaced with the system's temporary directory. - * - * @var string */ public string $classMapperDirectory = '{$TMP}' . \DIRECTORY_SEPARATOR . 'mappers'; /** * In debug mode, the generated mapper files are deleted as soon as mapping is done. * This let you edit mapped classes without having to worry about the mapper cache. - * - * @var bool */ public bool $debug = false; /** * If true, enums will be mapped using the `tryFrom` method instead of the `from` method. * This might result in null values being mapped to non-nullable fields. - * - * @var bool */ public bool $enumTryFrom = false; /** * If true, mapping a null value to a non-nullable field will throw an UnexpectedNullValueException. - * - * @var bool */ public bool $strictNullMapping = true; } diff --git a/src/Objects/ObjectMapper.php b/src/Objects/ObjectMapper.php index 51e7839..3866188 100644 --- a/src/Objects/ObjectMapper.php +++ b/src/Objects/ObjectMapper.php @@ -109,7 +109,13 @@ private function createObjectMappingFunction(ClassBluePrint $blueprint, string $ $propertyMap = $this->wrapDefault($propertyMap, $name, $property['default']); } - $content .= \PHP_EOL . $tab . $tab . '$x->' . $name . ' = ' . $propertyMap . ';'; + $propertySet = \PHP_EOL . $tab . $tab . '$x->' . $name . ' = ' . $propertyMap . ';'; + + if ($this->mapper->config->allowUninitializedFields && ! \array_key_exists('default', $property)) { + $propertySet = $this->wrapArrayKeyExists($propertySet, $name); + } + + $content .= $propertySet; } // Post mapping functions? @@ -184,6 +190,15 @@ private function wrapDefault(string $value, string $arrayKey, mixed $defaultValu return "(\\array_key_exists('{$arrayKey}', \$data) ? {$value} : " . \var_export($defaultValue, true) . ')'; } + private function wrapArrayKeyExists(string $expression, string $arrayKey): string + { + $content = \PHP_EOL . \str_repeat(' ', 2) . "if (\\array_key_exists('{$arrayKey}', \$data)) {"; + $content .= \str_replace(\PHP_EOL, \PHP_EOL . ' ', $expression) . \PHP_EOL; + $content .= \str_repeat(' ', 2) . '}'; + + return $content; + } + public function __destruct() { if ($this->mapper->config->debug) { From daa87e14b026390d5a49641914ab6152e6bae93c Mon Sep 17 00:00:00 2001 From: jerodev Date: Wed, 17 May 2023 12:35:41 +0200 Subject: [PATCH 2/4] Wrap foreach if initialized is allowed --- src/Objects/ObjectMapper.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Objects/ObjectMapper.php b/src/Objects/ObjectMapper.php index 3866188..c6264af 100644 --- a/src/Objects/ObjectMapper.php +++ b/src/Objects/ObjectMapper.php @@ -224,6 +224,10 @@ private function buildPropertyForeachMapping(string $propertyName, array $proper $foreach .= \PHP_EOL . \str_repeat(' ', 2) . '}'; } + if ($this->mapper->config->allowUninitializedFields && ! \array_key_exists('default', $property)) { + $foreach = $this->wrapArrayKeyExists($foreach, $propertyName); + } + return $foreach; } } From 386b39a04bbe52d4db8697c9a94d753e1751fe11 Mon Sep 17 00:00:00 2001 From: jerodev Date: Wed, 17 May 2023 12:36:49 +0200 Subject: [PATCH 3/4] Test with uninitialized properties --- tests/MapperTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/MapperTest.php b/tests/MapperTest.php index 4c29e11..35bd09f 100644 --- a/tests/MapperTest.php +++ b/tests/MapperTest.php @@ -150,5 +150,13 @@ public static function objectValuesDataProvider(): Generator ], $dto, ]; + + // Allow uninitialized properties + $dto = new Aliases(); + yield [ + Aliases::class, + [], + $dto, + ]; } } From 831227379bb96686a27463c880863b17312dbd40 Mon Sep 17 00:00:00 2001 From: jerodev Date: Wed, 17 May 2023 12:38:45 +0200 Subject: [PATCH 4/4] Add config key to readme --- readme.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index c0c721c..d1d96e5 100644 --- a/readme.md +++ b/readme.md @@ -83,12 +83,13 @@ The mapper comes with a few configuration options that can be set using the [`Ma object and passed to the mappers' constructor. This is not required, if no configuration is passed, the default config is used. -| Option | Type | Default | Description | -|------------------------|----------|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `classMapperDirectory` | `string` | `/tmp/mappers` | This is the location the mapper will create cached mapper functions for objects.
The default location is a mappers function in the operating system temporary folder. | -| `debug` | `bool` | `false` | Enabling debug will clear all cached mapper functions after mapping has completed. | -| `enumTryFrom` | `bool` | `false` | Enabling this will use the `::tryFrom()` method instead of `::from()` to parse strings to enums. | -| `strictNullMapping` | `bool` | `true` | If enabled, the mapper will throw an error when a `null` value is passed for a property that was not typed as nullable. | +| Option | Type | Default | Description | +|----------------------------|----------|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `allowUninitializedFields` | `bool` | `true` | If disabled, the mapper will fail if one of the class properties that does not have a default value was not present in the data array. | +| `classMapperDirectory` | `string` | `/tmp/mappers` | This is the location the mapper will create cached mapper functions for objects.
The default location is a mappers function in the operating system temporary folder. | +| `debug` | `bool` | `false` | Enabling debug will clear all cached mapper functions after mapping has completed. | +| `enumTryFrom` | `bool` | `false` | Enabling this will use the `::tryFrom()` method instead of `::from()` to parse strings to enums. | +| `strictNullMapping` | `bool` | `true` | If enabled, the mapper will throw an error when a `null` value is passed for a property that was not typed as nullable. | ## Under the hood For simple native types, the mapper will use casting to convert the data to the correct type.