Skip to content

Commit

Permalink
Merge branch 'feature/allow-unregistered-mappings'
Browse files Browse the repository at this point in the history
  • Loading branch information
mark-gerarts committed Feb 21, 2019
2 parents 3beb622 + 66fc750 commit 1db8f47
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ language: php
php:
- '7.1'
- '7.2'
- nightly
- '7.3'
install:
- composer install
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
<?php

$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.

### 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
Expand Down Expand Up @@ -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

Expand Down
25 changes: 23 additions & 2 deletions src/Configuration/AutoMapperConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
);
}

/**
Expand Down
26 changes: 26 additions & 0 deletions src/Configuration/Options.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ class Options
*/
private $ignoreNullProperties = false;

/**
* @var bool
*/
private $createUnregisteredMappings = false;

/**
* @return Options
*
Expand Down Expand Up @@ -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;
}
}
23 changes: 23 additions & 0 deletions test/AutoMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
11 changes: 11 additions & 0 deletions test/Configuration/AutoMapperConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
));
}
}

0 comments on commit 1db8f47

Please sign in to comment.