From 4f72651cb82b4ac32eefd36bb132774b242b4546 Mon Sep 17 00:00:00 2001 From: Mark Gerarts Date: Thu, 21 Feb 2019 21:21:43 +0100 Subject: [PATCH 1/2] Allow automatic creation of mappings --- README.md | 19 ++++++++++++++- src/Configuration/AutoMapperConfig.php | 25 ++++++++++++++++++-- src/Configuration/Options.php | 26 +++++++++++++++++++++ test/AutoMapperTest.php | 23 ++++++++++++++++++ test/Configuration/AutoMapperConfigTest.php | 11 +++++++++ 5 files changed, 101 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d20744e..cf1550d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Transfers data from one object to another, allowing custom mapping operations. * [Handling object construction](#handling-object-construction) * [ReverseMap](#reversemap) * [Copying a mapping](#copying-a-mapping) + * [Automatic creation of mappings](#automatic-creation-of-mappings) * [Resolving property names](#resolving-property-names) * [Naming conventions](#naming-conventions) * [Explicitly state source property](#explicitly-state-source-property) @@ -173,7 +174,7 @@ $mapper->mapMultiple($employees, EmployeeDto::class); ### Registering mappings Mappings are defined using the `AutoMapperConfig`'s `registerMapping()` method. -Every mapping has to be explicitly defined before you can use it. +By default, every mapping has to be explicitly defined before you can use it. A mapping is defined by providing the source class and the destination class. The most basic definition would be as follows: @@ -425,6 +426,21 @@ $listMapping = $config->registerMapping(Employee::class, EmployeeListView::class ->skipConstructor(); ``` +### Automatic creation of mappings +When you're dealing with very simple mappings that don't require any +configuration, it can be quite cumbersome the register a mapping for each and +every mapping. For these cases it is possible to enable the automatic creation +of mappings: + +```php +getOptions()->createUnregisteredMappings(); +``` + +With this configuration the mapper will generate a very basic mapping on the +fly instead of throwing an exception if the mapping is not configured. + ### Resolving property names Unless you define a specific way to fetch a value (e.g. `mapFrom`), the mapper has to have a way to know which source property to map from. By default, it will @@ -560,6 +576,7 @@ The available options that can be set are: | Object crates | `[\stdClass::class]` | See [the dedicated section](#the-concept-of-object-crates). | | Ignore null properties | false | Sets whether or not a source property should be mapped to the destination object if the source value is null | | Use substitution | true | Whether or not the Liskov substitution principle should be applied when resolving a mapping. | +| createUnregisteredMappings | false | Whether or not an exception should be thrown for unregistered mappings, or a mapping should be generated on the fly. | ### Setting the options diff --git a/src/Configuration/AutoMapperConfig.php b/src/Configuration/AutoMapperConfig.php index fc14eb8..809b2ab 100644 --- a/src/Configuration/AutoMapperConfig.php +++ b/src/Configuration/AutoMapperConfig.php @@ -64,7 +64,16 @@ public function getMappingFor( } if (!$this->options->shouldUseSubstitution()) { - return null; + if (!$this->options->shouldCreateUnregisteredMappings()) { + return null; + } + + // We don't use substitution (BC), but we allow creation of mappings + // on the fly. + return $this->registerMapping( + $sourceClassName, + $destinationClassName + ); } // We didn't find an exact match, and substitution is allowed. We'll @@ -78,11 +87,23 @@ function (MappingInterface $mapping) use ($sourceClassName, $destinationClassNam } ); - return $this->getMostSpecificCandidate( + $mapping = $this->getMostSpecificCandidate( $candidates, $sourceClassName, $destinationClassName ); + if ($mapping !== null) { + return $mapping; + } + + if (!$this->options->shouldCreateUnregisteredMappings()) { + return null; + } + + return $this->registerMapping( + $sourceClassName, + $destinationClassName + ); } /** diff --git a/src/Configuration/Options.php b/src/Configuration/Options.php index fc3244f..5707201 100644 --- a/src/Configuration/Options.php +++ b/src/Configuration/Options.php @@ -80,6 +80,11 @@ class Options */ private $ignoreNullProperties = false; + /** + * @var bool + */ + private $createUnregisteredMappings = false; + /** * @return Options * @@ -337,4 +342,25 @@ public function shouldIgnoreNullProperties(): bool { return $this->ignoreNullProperties; } + + public function createUnregisteredMappings(): void + { + $this->createUnregisteredMappings = true; + } + + public function dontCreateUnregisteredMappings(): void + { + $this->createUnregisteredMappings = false; + } + + /** + * Whether or not a mapping should be generated on the fly when trying to + * execute an unknown mapping. If not, an exception is thrown instead. + * + * @return bool + */ + public function shouldCreateUnregisteredMappings(): bool + { + return $this->createUnregisteredMappings; + } } diff --git a/test/AutoMapperTest.php b/test/AutoMapperTest.php index 712d420..0b3cfe0 100644 --- a/test/AutoMapperTest.php +++ b/test/AutoMapperTest.php @@ -4,6 +4,7 @@ use AutoMapperPlus\Configuration\AutoMapperConfig; use AutoMapperPlus\Configuration\AutoMapperConfigInterface; +use AutoMapperPlus\Exception\UnregisteredMappingException; use AutoMapperPlus\Test\CustomMapper\EmployeeMapper; use AutoMapperPlus\MappingOperation\Operation; use AutoMapperPlus\NameConverter\NamingConvention\CamelCaseNamingConvention; @@ -698,4 +699,26 @@ public function testitMapsPrivatePropertiesToStdClass() $this->assertEquals($result->username, 'AzureDiamond'); $this->assertEquals($result->password, 'hunter2'); } + + public function testAnExceptionIsThrownForUnregisteredMappings() + { + $this->expectException(UnregisteredMappingException::class); + + $mapper = new AutoMapper(); + $source = new Source('a name'); + + $mapper->map($source, Destination::class); + } + + public function testMappingsCanBeGeneratedOnTheFlyIfOptionIsSet() + { + $config = new AutoMapperConfig(); + $config->getOptions()->createUnregisteredMappings(); + $mapper = new AutoMapper($config); + $source = new Source('a name'); + + $result = $mapper->map($source, Destination::class); + + $this->assertEquals('a name', $result->name); + } } diff --git a/test/Configuration/AutoMapperConfigTest.php b/test/Configuration/AutoMapperConfigTest.php index 9ea745e..1b8e2b9 100644 --- a/test/Configuration/AutoMapperConfigTest.php +++ b/test/Configuration/AutoMapperConfigTest.php @@ -70,4 +70,15 @@ public function testSubstitutionPrincipleDestination() DestinationChild::class )); } + + public function testMappingsGetGeneratedOnTheFlyIfOptionSet() + { + $config = new AutoMapperConfig(); + $config->getOptions()->createUnregisteredMappings(); + + $this->assertTrue($config->hasMappingFor( + Source::class, + Destination::class + )); + } } From 66fc7509b2db579ad1dd4ea9fba0444eecf98ff2 Mon Sep 17 00:00:00 2001 From: Mark Gerarts Date: Thu, 21 Feb 2019 21:22:20 +0100 Subject: [PATCH 2/2] Don't use nightly PHP for travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 86f5a44..7c855bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,6 @@ language: php php: - '7.1' - '7.2' - - nightly + - '7.3' install: - composer install