diff --git a/README.md b/README.md index 09cbf83..04c2b04 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Transfers data from one object to another, allowing custom mapping operations. * [ReverseMap](#reversemap) * [Copying a mapping](#copying-a-mapping) * [Automatic creation of mappings](#automatic-creation-of-mappings) + * [Extending mappings](#extending-mappings) * [Resolving property names](#resolving-property-names) * [Naming conventions](#naming-conventions) * [Explicitly state source property](#explicitly-state-source-property) @@ -442,6 +443,21 @@ $config->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. +### Extending mappings +There are some cases when you want to use unregistered mappings but you want to setup +mapping just for one or few properties of the mapped class without need to define mapping for +each property. In this case you can simply extend mapping and define just those properties +that you need to have mapped differently: + +```php +extendMapping(Employee::class, EmployeeListView::class) + ->forMember('name', function (Employee $employee) { + return strtoupper($employee->name); + }); +``` + ### 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 diff --git a/src/Configuration/AutoMapperConfig.php b/src/Configuration/AutoMapperConfig.php index 0badcac..e0258bc 100644 --- a/src/Configuration/AutoMapperConfig.php +++ b/src/Configuration/AutoMapperConfig.php @@ -202,6 +202,22 @@ public function registerMapping( return $mapping; } + /** + * @inheritdoc + */ + public function extendMapping( + string $sourceClassName, + string $destinationClassName + ): MappingInterface { + if (!$this->hasMappingFor($sourceClassName, $destinationClassName)) { + throw new \RuntimeException("Undefined mapping for class $sourceClassName"); + } + + $mapping = $this->getMappingFor($sourceClassName, $destinationClassName); + + return $this->registerMapping($sourceClassName, $destinationClassName)->copyFromMapping($mapping); + } + /** * @inheritdoc */ diff --git a/src/Configuration/AutoMapperConfigInterface.php b/src/Configuration/AutoMapperConfigInterface.php index 7eb6d07..a0186cc 100644 --- a/src/Configuration/AutoMapperConfigInterface.php +++ b/src/Configuration/AutoMapperConfigInterface.php @@ -47,6 +47,19 @@ public function registerMapping( string $destinationClassName ): MappingInterface; + /** + * Extends already registered mapping. Whether it's unregistered type + * of mapping or manually registered mapping. + * + * @param string $sourceClassName + * @param string $destinationClassName + * @return MappingInterface + */ + public function extendMapping( + string $sourceClassName, + string $destinationClassName + ): MappingInterface; + /** * @return Options */ diff --git a/test/AutoMapperTest.php b/test/AutoMapperTest.php index 8cc3f61..cd6b5a8 100644 --- a/test/AutoMapperTest.php +++ b/test/AutoMapperTest.php @@ -150,6 +150,40 @@ public function testItCanMapWithACallback() $this->assertEquals('NewName', $destination->name); } + public function testItCanExtendMapping() + { + $this->config->registerMapping(Source::class, Destination::class); + $mapper = new AutoMapper($this->config); + $source = new Source(); + $source->name = 'John'; + $destination = $mapper->map($source, Destination::class); + + $this->assertEquals('John', $destination->name); + + $this->config->extendMapping(Source::class, Destination::class) + ->forMember('name', function (Source $source) { + return $source->name . ' ' . 'Doe'; + }); + $destination = $mapper->map($source, Destination::class); + + $this->assertEquals('John Doe', $destination->name); + } + + public function testItThrowsRuntimeErrorOnExtendMappingWhenDoesntHaveMapping() + { + $this->expectException(\RuntimeException::class); + + $this->config->getOptions()->dontCreateUnregisteredMappings(); + $mapper = new AutoMapper($this->config); + $this->config->extendMapping(Source::class, Destination::class) + ->forMember('name', function (Source $source) { + return $source->name . ' ' . 'Doe'; + }); + $source = new Source(); + $source->name = 'John'; + $mapper->map($source, Destination::class); + } + public function testTheConfigurationCanBeRetrieved() { $config = new AutoMapperConfig();