Skip to content

Commit

Permalink
feat: add attributes for mapping configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
phramz committed Jun 14, 2024
1 parent 2dc795c commit d01ee27
Show file tree
Hide file tree
Showing 23 changed files with 789 additions and 317 deletions.
17 changes: 9 additions & 8 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "backbrain/php-automapper",
"description": "PHP port of the popular automapper.org library",
"keywords": ["automapper", "mapping", "object", "mapper", "mapping", "object-mapper"],
"keywords": ["automapper", "mapping", "object", "mapper", "mapping", "object-mapper", "symfony"],
"homepage": "https://backbrainhq.github.io/php-automapper/",
"type": "library",
"license": "MIT",
Expand All @@ -18,7 +18,7 @@
"authors": [
{
"name": "Maximilian Reichel",
"email": "hello@backbrain.io"
"email": "automapper@backbrain.io"
}
],
"require": {
Expand All @@ -27,10 +27,15 @@
"psr/cache": "^3.0",
"symfony/property-access": "^6.0|^7.0",
"symfony/property-info": "^6.0|^7.0",
"symfony/finder": "^6.0|^7.0",
"symfony/expression-language": "^6.0|^7.0",
"symfony/uid": "^6.0|^7.0",
"symfony/clock": "^6.0|^7.0",
"phpstan/phpdoc-parser": "^1.26",
"phpdocumentor/type-resolver": "^1.8",
"phpdocumentor/reflection-docblock": "^5.3",
"jawira/case-converter": "^3.5"
"jawira/case-converter": "^3.5",
"nikic/php-parser": "^4.0|^5.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0",
Expand All @@ -39,10 +44,6 @@
"symfony/var-dumper": "*",
"doctrine/collections": "^2.2",
"symfony/framework-bundle": "^6.3|^7.0",
"symfony/filesystem": "^6.3|^7.0",
"symfony/expression-language": "^6.3|^7.0"
},
"suggest": {
"symfony/expression-language": "To use the FromExpr attribute"
"symfony/filesystem": "^6.3|^7.0"
}
}
2 changes: 1 addition & 1 deletion src/AutoMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private function mapType(MapInterface $map, mixed $srcValue, mixed $destValue):
continue;
}

if (null !== $member->getCondition() && !$member->getCondition()($srcValue)) {
if (null !== $member->getCondition() && !$member->getCondition()($srcValue, $this->newResolutionContext(map: $map, source: $srcValue))) {
$this->logger->debug('Member condition returned false.', [
'source' => $srcValue,
'destination' => $destValue,
Expand Down
8 changes: 6 additions & 2 deletions src/BaseMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ protected function mustGetMap(array $maps, string $sourceType, string $destinati
{
$map = $this->getMap($maps, $sourceType, $destinationType);
if (null === $map) {
throw MapperException::newMissingMapException($sourceType, $destinationType);
throw MapperException::newMissingMapException($this->canonicalize($sourceType), $this->canonicalize($destinationType));
}

return $map;
Expand Down Expand Up @@ -279,7 +279,11 @@ protected function parseTypeExpr(string $type): array
protected function canonicalize(string $type): string
{
// we want to have no whitespaces
return ltrim(str_replace(' ', '', str_replace('\\\\', '\\', trim($type))), '\\');
$type = ltrim(str_replace(' ', '', str_replace('\\\\', '\\', trim($type))), '\\');

// TODO find a better way to handle Proxies\\__CG__\\ namespace and make it somehow configurable
// strip Proxies\\__CG__\\ prefix from type
return preg_replace('/^Proxies\\\__CG__\\\/', '', $type) ?? $type;
}

protected function isType(mixed $value, string $expectedType): bool
Expand Down
12 changes: 0 additions & 12 deletions src/Bundle/DependencyInjection/AutomapperCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,5 @@ public function process(ContainerBuilder $container): void
foreach ($container->findTaggedServiceIds('backbrain_automapper_profile') as $id => $tags) {
$factoryDefinition->addMethodCall('addProfile', [new Reference($id)]);
}

foreach ($container->findTaggedServiceIds('backbrain_automapper_model') as $id => $tags) {
$def = $container->findDefinition($id);

foreach ($tags as $tag) {
$factoryDefinition->addMethodCall('addModel', [
'$dest' => $def->getClass(),
'$source' => $tag['source'] ?? null,
'$include' => $tag['include'] ?? null,
]);
}
}
}
}
71 changes: 58 additions & 13 deletions src/Bundle/DependencyInjection/AutomapperExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
namespace Backbrain\Automapper\Bundle\DependencyInjection;

use Backbrain\Automapper\Contract\Attributes\AsProfile;
use Backbrain\Automapper\Contract\Attributes\Source;
use Backbrain\Automapper\Helper\ClassNameVisitor;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\ParserFactory;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
Expand All @@ -14,6 +17,8 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class AutomapperExtension extends Extension implements ConfigurationInterface
Expand All @@ -25,25 +30,27 @@ public function load(array $configs, ContainerBuilder $container): void

$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);

$factoryDefinition = $container->findDefinition('backbrain_automapper_factory');

$cacheAdapterServiceId = $config['cache_adapter'];
$cacheAdapterServiceId = $config['metadata_cache_adapter'];
$cacheExpressionLanguageServiceId = $config['expression_language'];
$loggerServiceId = $config['logger'];

$factoryDefinition->addMethodCall('setCacheItemPool', [new Reference($cacheAdapterServiceId)]);
$factoryDefinition->addMethodCall('setLogger', [new Reference($loggerServiceId)]);
$factoryDefinition->replaceArgument('$logger', new Reference($loggerServiceId));
$factoryDefinition->replaceArgument('$cacheItemPool', new Reference($cacheAdapterServiceId));
$factoryDefinition->replaceArgument('$expressionLanguage', new Reference($cacheExpressionLanguageServiceId));

if (count($config['paths']) > 0) {
foreach ($this->scanPath(...$config['paths']) as $className) {
$factoryDefinition->addMethodCall('addClass', [
'$className' => $className,
]);
}
}

$container->registerAttributeForAutoconfiguration(AsProfile::class, static function (ChildDefinition $definition, AsProfile $attribute) {
$definition->addTag('backbrain_automapper_profile');
});

$container->registerAttributeForAutoconfiguration(Source::class, static function (ChildDefinition $definition, Source $attribute) {
$definition->addTag('backbrain_automapper_model', [
'source' => $attribute->getSource(),
'include' => $attribute->getInclude(),
]);
});
}

/**
Expand All @@ -67,10 +74,48 @@ public function getConfigTreeBuilder(): TreeBuilder

// @phpstan-ignore-next-line
$rootNode->children()
->scalarNode('cache_adapter')->defaultValue('cache.app')->end()
->scalarNode('metadata_cache_adapter')->defaultValue('cache.system')->end()
->scalarNode('expression_language')->defaultValue('security.expression_language')->end()
->scalarNode('logger')->defaultValue('logger')->end()
->arrayNode('paths')
->scalarPrototype()->end()
->end()
;

return $treeBuilder;
}

/**
* @return class-string[]
*/
public function scanPath(string ...$path): array
{
$parserFactory = new ParserFactory();
$parser = method_exists($parserFactory, 'createForNewestSupportedVersion')
? $parserFactory->createForNewestSupportedVersion()
: $parserFactory->create(ParserFactory::PREFER_PHP7); // @phpstan-ignore-line

$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver());

$finder = new Finder();
$finder->files()->in($path)->name('*.php');

$classes = [];
/** @var SplFileInfo $file */
foreach ($finder as $file) {
$ast = $parser->parse($file->getContents());

$classNameVisitor = new ClassNameVisitor();
$traverser->addVisitor($classNameVisitor);

$traverser->traverse($ast);

foreach ($classNameVisitor->getClassNames() as $className) {
$classes[] = $className;
}
}

return $classes;
}
}
172 changes: 0 additions & 172 deletions src/Bundle/Factory.php

This file was deleted.

Loading

0 comments on commit d01ee27

Please sign in to comment.