Skip to content

Commit

Permalink
Add middleware feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Toilal committed Sep 12, 2019
1 parent a0a7d7a commit 18ed9e4
Show file tree
Hide file tree
Showing 22 changed files with 876 additions and 33 deletions.
43 changes: 42 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 @@ -805,6 +806,46 @@ $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 mapProperty($propertyName,
$source,
$destination,
AutoMapperInterface $mapper,
MappingInterface $mapping,
MappingOperationInterface $operation,
array $context,
callable $next)
{
if ($propertyName === 'id') {
$defaultValue = $mapping->getOptions()->getPropertyReader()->getProperty($destination, $propertyName);
if ($defaultValue === NULL) {
$mapping->getOptions()->getPropertyWriter()->setProperty($destination, $propertyName, 42);
}
}
$next();
}
}

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

### 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 @@ -936,7 +977,7 @@ Please note that this is a temporary solution. The issue will be fixed in the
- [ ] 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*
43 changes: 19 additions & 24 deletions src/AutoMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
use AutoMapperPlus\Exception\InvalidArgumentException;
use AutoMapperPlus\Exception\UnregisteredMappingException;
use AutoMapperPlus\Exception\UnsupportedSourceTypeException;
use AutoMapperPlus\MappingOperation\ContextAwareOperation;
use AutoMapperPlus\MappingOperation\MapperAwareOperation;
use AutoMapperPlus\Middleware\MapperMiddleware;

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

return $this->doMap(
$source,
$destination,
Expand All @@ -188,7 +188,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 @@ -204,29 +204,24 @@ protected function doMap(
array $context = []
)
{
$propertyNames = $mapping->getTargetProperties($destination, $source);
foreach ($propertyNames as $propertyName) {
$this->push(self::PROPERTY_STACK_CONTEXT, $propertyName, $context);
try {
$mappingOperation = $mapping->getMappingOperationFor($propertyName);

if ($mappingOperation instanceof MapperAwareOperation) {
$mappingOperation->setMapper($this);
}
if ($mappingOperation instanceof ContextAwareOperation) {
$mappingOperation->setContext($context);
}

$mappingOperation->mapProperty(
$propertyName,
$source,
$destination
);
} finally {
$this->pop(self::PROPERTY_STACK_CONTEXT, $context);
}
$mapper = $this;

$this->autoMapperConfig->getDefaultMapperMiddleware()->map($source, $destination, $mapper, $mapping, $context, function () {
});

$map = function () {
// NOOP
};

foreach (array_reverse($this->getConfiguration()->getMapperMiddlewares()) as $middleware) {
$map = function () use ($middleware, $source, $destination, $mapper, $mapping, $context, $map) {
/** @var MapperMiddleware $middleware */
return $middleware->map($source, $destination, $mapper, $mapping, $context, $map);
};
}

$map();

return $destination;
}

Expand Down
97 changes: 90 additions & 7 deletions src/Configuration/AutoMapperConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

namespace AutoMapperPlus\Configuration;

use AutoMapperPlus\Middleware\DefaultMapperMiddleware;
use AutoMapperPlus\Middleware\DefaultMiddleware;
use AutoMapperPlus\Middleware\DefaultPropertyMiddleware;
use AutoMapperPlus\Middleware\MapperMiddleware;
use AutoMapperPlus\Middleware\Middleware;
use AutoMapperPlus\Middleware\PropertyMiddleware;

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

/** @var MapperMiddleware */
private $defaultMapperMiddleware;

/** @var PropertyMiddleware */
private $defaultPropertyMiddleware;

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

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

/**
* @var Options
*/
Expand All @@ -30,6 +53,8 @@ public function __construct(callable $configurator = null)
if ($configurator !== null) {
$configurator($this->options);
}
$this->defaultMapperMiddleware = new DefaultMapperMiddleware();
$this->defaultPropertyMiddleware = new DefaultPropertyMiddleware();
}

/**
Expand All @@ -38,7 +63,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 +79,8 @@ public function hasMappingFor(
public function getMappingFor(
string $sourceClassName,
string $destinationClassName
): ?MappingInterface {
): ?MappingInterface
{
// Check for an exact match before we try parent classes.
$mapping = $this->mappings["$sourceClassName|$destinationClassName"] ?? NULL;
if ($mapping) {
Expand Down Expand Up @@ -117,11 +144,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 @@ -151,14 +179,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 @@ -191,7 +220,8 @@ protected function getClassDistance(
public function registerMapping(
string $sourceClassName,
string $destinationClassName
): MappingInterface {
): MappingInterface
{
$mapping = new Mapping(
$sourceClassName,
$destinationClassName,
Expand All @@ -202,11 +232,64 @@ public function registerMapping(
return $mapping;
}


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

return $this;
}

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

/**
* @return PropertyMiddleware
*/
public function getDefaultPropertyMiddleware(): PropertyMiddleware
{
return $this->defaultPropertyMiddleware;
}

/**
* @return MapperMiddleware
*/
public function getDefaultMapperMiddleware(): MapperMiddleware
{
return $this->defaultMapperMiddleware;
}

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

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

namespace AutoMapperPlus\Configuration;

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

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

/**
* Register middlewares after existing ones.
*
* All middlewares will be invoked in order.
*
* @param Middleware ...$middlewares
* @return self
*
* @see DefaultMiddleware
* @see PropertyMiddleware
* @see MapperMiddleware
*/
public function registerMiddlewares(Middleware ...$middlewares): AutoMapperConfigInterface;

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

/**
* @return MapperMiddleware
*/
public function getDefaultMapperMiddleware();

/**
* @return PropertyMiddleware
*/
public function getDefaultPropertyMiddleware();

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

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

0 comments on commit 18ed9e4

Please sign in to comment.