Skip to content
Closed
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
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Transfers data from one object to another, allowing custom mapping operations.
* [The concept of object crates](#the-concept-of-object-crates)
* [Mapping with arrays](#mapping-with-arrays)
* [Using a custom mapper](#using-a-custom-mapper)
* [Using middlewares](#using-middlewares)
* [Adding context](#adding-context)
* [Misc](#misc)
* [Similar libraries](#similar-libraries)
Expand Down Expand Up @@ -804,6 +805,55 @@ $employee = new Employee(10, 'John', 'Doe', 1980);
$result = $mapper->map($employee, EmployeeDto::class);
```

### Using middlewares
You can register middlewares to customize how automapper works internally and define
global behaviors.

The following example will set 42 to any `id` property that would have been `null`.

```php
<?php

class AnwserToUniverseMiddleware implements PropertyMiddleware
{
public function supportsMapProperty($propertyName,
$source,
$destination,
MappingInterface $mapping,
MappingOperationInterface $operation,
array $context = [])
{
return $propertyName == 'id';
}

public function mapProperty($propertyName,
$source,
$destination,
MappingInterface $mapping,
MappingOperationInterface $operation,
array $context = [])
{
$defaultValue = $mapping->getOptions()->getPropertyReader()->getProperty($destination, $propertyName);
if ($defaultValue === NULL) {
$mapping->getOptions()->getPropertyWriter()->setProperty($destination, $propertyName, 42);
}
}
}

$config->registerMiddlewares(new AnwserToUniverseMiddleware());
$config->registerMapping(Employee::class, EmployeeDto::class);
$mapper = new AutoMapper($config);

// The AutoMapper can now be used as usual, but your middleware will intercept some property mappings.
$employee = new Employee(NULL, 'John', 'Doe', 1980);
$result = $mapper->map($employee, EmployeeDto::class);
echo $result->id; // => 42
```

Middleware feature open up many extension capabilities, feel free to check PHPDocs
from [PropertyMiddleware](./src/Middleware/PropertyMiddleware.php) and
[MapperMiddleware](./src/Middleware/MapperMiddleware.php) interfaces for details.

### Adding context
Sometimes a mapping should behave differently based on the context. It is
therefore possible to pass a third argument to the map methods to describe
Expand Down Expand Up @@ -915,7 +965,7 @@ where needed, without needing to change the code that uses the mapper.
- [ ] Allow setting a maximum depth, see #14
- [ ] Provide a NameResolver that accepts an array mapping, as an alternative to multiple `FromProperty`s
- [ ] Make use of a decorated Symfony's `PropertyAccessor` (see [#16](https://github.com/mark-gerarts/automapper-plus/issues/16))
- [ ] Allow adding of middleware to the mapper
- [x] Allow adding of middleware to the mapper
- [ ] Allow mapping *to* array

*[Version 2](https://github.com/mark-gerarts/automapper-plus/tree/2.0) is in the works, check there for new features as well*
122 changes: 114 additions & 8 deletions src/AutoMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
use AutoMapperPlus\Exception\UnsupportedSourceTypeException;
use AutoMapperPlus\MappingOperation\ContextAwareOperation;
use AutoMapperPlus\MappingOperation\MapperAwareOperation;
use AutoMapperPlus\MappingOperation\MappingOperationInterface;
use AutoMapperPlus\Middleware\MapperMiddleware;
use AutoMapperPlus\Middleware\Middleware;
use AutoMapperPlus\Middleware\PropertyMiddleware;

/**
* Class AutoMapper
Expand Down Expand Up @@ -164,7 +168,7 @@ public function mapToObject($source, $destination, array $context = [])
}

/**
* Performs the actual transferring of properties.
* Performs the actual transferring of properties, involving all matching mapper and property middleware.
*
* @param $source
* @param $destination
Expand All @@ -179,6 +183,44 @@ protected function doMap(
MappingInterface $mapping,
array $context = []
) {
$mapperMiddlewares = $this->getMapperMiddlewares($source, $destination, $mapping, $context);
foreach ($mapperMiddlewares[Middleware::BEFORE] as $middleware) {
$middleware->map($source, $destination, $mapping, $context);
}

$overrideMiddlewares = $mapperMiddlewares[Middleware::OVERRIDE];
if ($overrideMiddlewares) {
foreach ($overrideMiddlewares as $middleware) {
$middleware->map($source, $destination, $mapping, $context);
}
} else {
$this->doMapDefault($source, $destination, $mapping, $context);
}

foreach ($mapperMiddlewares[Middleware::AFTER] as $middleware) {
$middleware->map($source, $destination, $mapping, $context);
}

return $destination;
}

/**
* Performs the actual default transferring of properties, involving all registered property middleware.
*
* @param $source
* @param $destination
* @param MappingInterface $mapping
* @param array $context
* @return mixed
* The destination object with mapped properties.
*/
protected function doMapDefault(
$source,
$destination,
MappingInterface $mapping,
array $context = []
)
{
$propertyNames = $mapping->getTargetProperties($destination, $source);
foreach ($propertyNames as $propertyName) {
$mappingOperation = $mapping->getMappingOperationFor($propertyName);
Expand All @@ -190,14 +232,28 @@ protected function doMap(
$mappingOperation->setContext($context);
}

$mappingOperation->mapProperty(
$propertyName,
$source,
$destination
);
}
$propertyMiddlewares = $this->getPropertyMiddlewares($propertyName, $source, $destination, $mapping, $mappingOperation, $context);
foreach ($propertyMiddlewares[Middleware::BEFORE] as $middleware) {
$middleware->mapProperty($propertyName, $source, $destination, $mapping, $mappingOperation, $context);
}

return $destination;
$overrideMiddlewares = $propertyMiddlewares[Middleware::OVERRIDE];
if ($overrideMiddlewares) {
foreach ($overrideMiddlewares as $middleware) {
$middleware->mapProperty($propertyName, $source, $destination, $mapping, $mappingOperation, $context);
}
} else {
$mappingOperation->mapProperty(
$propertyName,
$source,
$destination
);
}

foreach ($propertyMiddlewares[Middleware::AFTER] as $middleware) {
$middleware->mapProperty($propertyName, $source, $destination, $mapping, $mappingOperation, $context);
}
}
}

/**
Expand Down Expand Up @@ -248,4 +304,54 @@ private function getCustomMapper(MappingInterface $mapping): ?MapperInterface

return $customMapper;
}

/**
* @param $source
* @param $destination
* @param string $propertyName
* @param MappingInterface $mapping
* @param MappingOperationInterface $mappingOperation
* @param array $context
* @return PropertyMiddleware[][]
*/
private function getPropertyMiddlewares(
$propertyName,
$source,
$destination,
MappingInterface $mapping,
MappingOperationInterface $mappingOperation,
array $context = []): array
{
$propertyMiddleware = [Middleware::AFTER => [], Middleware::OVERRIDE => NULL, Middleware::BEFORE => []];
foreach ($this->getConfiguration()->getPropertyMiddlewares() as $middleware) {
$supports = intval($middleware->supportsMapProperty($propertyName, $source, $destination, $mapping, $mappingOperation, $context));
if ($supports != Middleware::SKIP) {
$propertyMiddleware[$supports][] = $middleware;
}
}
return $propertyMiddleware;
}

/**
* @param $source
* @param $destination
* @param MappingInterface $mapping
* @param array $context
* @return MapperMiddleware[][]
*/
private function getMapperMiddlewares(
$source,
$destination,
MappingInterface $mapping,
array $context = []): array
{
$mapperMiddlewares = [Middleware::AFTER => [], Middleware::OVERRIDE => [], Middleware::BEFORE => []];
foreach ($this->getConfiguration()->getMapperMiddlewares() as $middleware) {
$supports = intval($middleware->supportsMap($source, $destination, $mapping, $context));
if ($supports != Middleware::SKIP) {
$mapperMiddlewares[$supports][] = $middleware;
}
}
return $mapperMiddlewares;
}
}
62 changes: 55 additions & 7 deletions src/Configuration/AutoMapperConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

namespace AutoMapperPlus\Configuration;

use AutoMapperPlus\Middleware\MapperMiddleware;
use AutoMapperPlus\Middleware\Middleware;
use AutoMapperPlus\Middleware\PropertyMiddleware;

/**
* Class AutoMapperConfig
*
Expand All @@ -14,6 +18,16 @@ class AutoMapperConfig implements AutoMapperConfigInterface
*/
private $mappings = [];

/**
* @var MapperMiddleware[]
*/
private $mapperMiddlewares = [];

/**
* @var PropertyMiddleware[]
*/
private $propertyMiddlewares = [];

/**
* @var Options
*/
Expand All @@ -38,7 +52,8 @@ public function __construct(callable $configurator = null)
public function hasMappingFor(
string $sourceClassName,
string $destinationClassName
): bool {
): bool
{
$mapping = $this->getMappingFor(
$sourceClassName,
$destinationClassName
Expand All @@ -53,7 +68,8 @@ public function hasMappingFor(
public function getMappingFor(
string $sourceClassName,
string $destinationClassName
): ?MappingInterface {
): ?MappingInterface
{
// Check for an exact match before we try parent classes.
foreach ($this->mappings as $mapping) {
$isExactMatch = $mapping->getSourceClassName() === $sourceClassName
Expand Down Expand Up @@ -120,11 +136,12 @@ protected function getMostSpecificCandidate(
array $candidates,
string $sourceClassName,
string $destinationClassName
): ?MappingInterface {
): ?MappingInterface
{
$lowestDistance = PHP_INT_MAX;
$selectedCandidate = null;
/** @var MappingInterface $candidate */
foreach($candidates as $candidate) {
foreach ($candidates as $candidate) {
$sourceDistance = $this->getClassDistance(
$sourceClassName,
$candidate->getSourceClassName()
Expand Down Expand Up @@ -154,14 +171,15 @@ protected function getMostSpecificCandidate(
protected function getClassDistance(
string $childClass,
string $parentClass
): int {
): int
{
if ($childClass === $parentClass) {
return 0;
}

$result = 0;
$childParents = class_parents($childClass, true);
foreach($childParents as $childParent) {
foreach ($childParents as $childParent) {
$result++;
if ($childParent === $parentClass) {
return $result;
Expand Down Expand Up @@ -194,7 +212,8 @@ protected function getClassDistance(
public function registerMapping(
string $sourceClassName,
string $destinationClassName
): MappingInterface {
): MappingInterface
{
$mapping = new Mapping(
$sourceClassName,
$destinationClassName,
Expand All @@ -205,11 +224,40 @@ public function registerMapping(
return $mapping;
}

public function registerMiddlewares(Middleware ...$middlewares): void
{
foreach ($middlewares as $middleware) {
if ($middleware instanceof MapperMiddleware) {
$this->mapperMiddlewares[] = $middleware;
}
if ($middleware instanceof PropertyMiddleware) {
$this->propertyMiddlewares[] = $middleware;
}
}
}


/**
* @inheritdoc
*/
public function getOptions(): Options
{
return $this->options;
}

/**
* @inheritdoc
*/
public function getMapperMiddlewares()
{
return $this->mapperMiddlewares;
}

/**
* @inheritdoc
*/
public function getPropertyMiddlewares()
{
return $this->propertyMiddlewares;
}
}
22 changes: 22 additions & 0 deletions src/Configuration/AutoMapperConfigInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

namespace AutoMapperPlus\Configuration;

use AutoMapperPlus\Middleware\MapperMiddleware;
use AutoMapperPlus\Middleware\Middleware;
use AutoMapperPlus\Middleware\PropertyMiddleware;

/**
* Interface AutoMapperConfigInterface
*
Expand Down Expand Up @@ -47,8 +51,26 @@ public function registerMapping(
string $destinationClassName
): MappingInterface;

/**
* Register middlewares.
*
* @param Middleware ...$middlewares
* @return void
*/
public function registerMiddlewares(Middleware ...$middlewares): void;

/**
* @return Options
*/
public function getOptions(): Options;

/**
* @return MapperMiddleware[]
*/
public function getMapperMiddlewares();

/**
* @return PropertyMiddleware[]
*/
public function getPropertyMiddlewares();
}
Loading