Skip to content

Commit

Permalink
feat: attribute metadata integration and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
phramz committed Jun 20, 2024
1 parent 542180a commit e9bb44e
Show file tree
Hide file tree
Showing 43 changed files with 984 additions and 325 deletions.
36 changes: 21 additions & 15 deletions .github/workflows/pull_request_open.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ concurrency:
group: '${{ github.workflow }}-${{ github.ref_name }}'
cancel-in-progress: true

env:
PHP_CS_FIXER_IGNORE_ENV: 1
COMPOSER_IGNORE_PLATFORM_REQS: 1

jobs:
lint:
runs-on: ubuntu-latest
Expand All @@ -19,33 +23,37 @@ jobs:
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
ini-values: "post_max_size=256M"
php-version: '8.3'
- name: Install Dependencies
run: make vendors
env:
COMPOSER_IGNORE_PLATFORM_REQS: 1
- name: Lint
run: php vendor/bin/php-cs-fixer fix --dry-run --diff
env:
PHP_CS_FIXER_IGNORE_ENV: 1
run: php vendor/bin/php-cs-fixer fix --dry-run --diff --format gitlab

phpstan:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
setup:
- php: '8.2'
vendors: 'high-deps'
- php: '8.2'
vendors: 'low-deps'
- php: '8.3'
vendors: 'high-deps'
- php: '8.3'
vendors: 'low-deps'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
ini-values: "post_max_size=256M"
php-version: ${{ matrix.setup.php }}
- name: Install Dependencies
run: make vendors
env:
COMPOSER_IGNORE_PLATFORM_REQS: 1
- name: Analyse
run: make ${{ matrix.setup.vendors }}
- name: Test
run: php vendor/bin/phpstan --memory-limit=512M analyse

unit:
Expand All @@ -72,8 +80,6 @@ jobs:
php-version: ${{ matrix.setup.php }}
- name: Install Dependencies
run: make ${{ matrix.setup.vendors }}
env:
COMPOSER_IGNORE_PLATFORM_REQS: 1
- name: Test
run: XDEBUG_MODE=coverage vendor/bin/phpunit -d memory_limit=256M --coverage-text

Expand Down
14 changes: 12 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,20 @@ lint-fix: _info
php vendor/bin/php-cs-fixer fix --diff

.PHONY: test
test: _info _test-stan _test-unit-low-deps _test-unit-high-deps
test: _info _test-stan _test-unit

.PHONY: _test-unit
_test-unit: _test-unit-low-deps _test-unit-high-deps

.PHONY: _test-stan
_test-stan:
_test-stan: _test-stan-low-deps _test-stan-high-deps

.PHONY: _test-stan-high-deps
_test-stan-high-deps: high-deps
vendor/bin/phpstan --memory-limit=512M analyse

.PHONY: _test-stan-low-deps
_test-stan-low-deps: low-deps
vendor/bin/phpstan --memory-limit=512M analyse

.PHONY: _test-unit-high-deps
Expand Down
3 changes: 3 additions & 0 deletions src/Builder/DefaultConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
use Backbrain\Automapper\Contract\MapInterface;
use Backbrain\Automapper\Contract\ProfileInterface;

/**
* @internal This class is not part of the public API and may change at any time!
*/
class DefaultConfig implements Config
{
/**
Expand Down
3 changes: 3 additions & 0 deletions src/Builder/DefaultMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
use Backbrain\Automapper\Helper\Func;
use Backbrain\Automapper\Model\Map;

/**
* @internal This class is not part of the public API and may change at any time!
*/
class DefaultMap implements \Backbrain\Automapper\Contract\Builder\Map
{
private ?TypeConverterInterface $typeConverter = null;
Expand Down
3 changes: 3 additions & 0 deletions src/Builder/DefaultOptionsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
use Backbrain\Automapper\Helper\Func;
use Backbrain\Automapper\Model\Member;

/**
* @internal This class is not part of the public API and may change at any time!
*/
class DefaultOptionsBuilder implements Options
{
private string $destinationPropertyPath;
Expand Down
43 changes: 2 additions & 41 deletions src/Bundle/DependencyInjection/AutomapperExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
namespace Backbrain\Automapper\Bundle\DependencyInjection;

use Backbrain\Automapper\Contract\Attributes\AsProfile;
use Backbrain\Automapper\Helper\ClassNameVisitor;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\ParserFactory;
use Backbrain\Automapper\Metadata\DirectoryMetadataProvider;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
Expand All @@ -17,8 +14,6 @@
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 @@ -41,7 +36,7 @@ public function load(array $configs, ContainerBuilder $container): void
$factoryDefinition->replaceArgument('$expressionLanguage', new Reference($cacheExpressionLanguageServiceId));

if (count($config['paths']) > 0) {
foreach ($this->scanPath(...$config['paths']) as $className) {
foreach ((new DirectoryMetadataProvider())->scanPath(...$config['paths']) as $className) {
$factoryDefinition->addMethodCall('addClass', [
'$className' => $className,
]);
Expand Down Expand Up @@ -84,38 +79,4 @@ public function getConfigTreeBuilder(): TreeBuilder

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;
}
}
21 changes: 12 additions & 9 deletions src/Contract/Attributes/Condition.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
<?php

declare(strict_types=1);

namespace Backbrain\Automapper\Contract\Attributes;

use Symfony\Component\ExpressionLanguage\Expression;

#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class Condition
{
private string $source;

private string $condition;
private Expression $expression;

/**
* Configure the mapping for a specific member.
Expand All @@ -17,32 +21,31 @@ class Condition
* // see https://symfony.com/doc/current/reference/formats/expression_language.html
* class AccountDTO {
* #[ForMember(ProfileDTO::class,
* mapFrom: 'source.givenName~" "~source.givenName',
* condition: 'source.publicProfile'
* )]
* public string $displayName;
* public string $email;
* }
* </code>
* Within the expression you can use the following variables:
* - `source`: the source object
* - `context`: the current `Backbrain\Automapper\Contracts\ResolutionContextInterface`.
*
* @param string $source the source member type for which this member configuration is applied
* @param string $condition a Symfony EL expression that must evaluate to `true` to map the member
* @param string $source the source member type for which this member configuration is applied
* @param string|Expression $expression a Symfony EL expression that must evaluate to `true` to map the member
*/
public function __construct(string $source, string $condition)
public function __construct(string $source, string|Expression $expression)
{
$this->source = $source;
$this->condition = $condition;
$this->expression = is_string($expression) ? new Expression($expression) : $expression;
}

public function getSource(): string
{
return $this->source;
}

public function getCondition(): string
public function getExpression(): Expression
{
return $this->condition;
return $this->expression;
}
}
11 changes: 4 additions & 7 deletions src/Contract/Attributes/ConstructUsing.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,17 @@
#[\Attribute(\Attribute::TARGET_CLASS)]
class ConstructUsing
{
/**
* @var TypeFactoryInterface|class-string<TypeFactoryInterface>
*/
private TypeFactoryInterface|string $constructUsing;
private TypeFactoryInterface $constructUsing;

/**
* @param TypeFactoryInterface|class-string<TypeFactoryInterface> $constructUsing Specifies a factory to use to create the destination object
* @param TypeFactoryInterface $constructUsing Specifies a factory to use to create the destination object
*/
public function __construct(TypeFactoryInterface|string $constructUsing)
public function __construct(TypeFactoryInterface $constructUsing)
{
$this->constructUsing = $constructUsing;
}

public function getConstructUsing(): string|TypeFactoryInterface
public function getConstructUsing(): TypeFactoryInterface
{
return $this->constructUsing;
}
Expand Down
2 changes: 2 additions & 0 deletions src/Contract/Attributes/Ignore.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Backbrain\Automapper\Contract\Attributes;

#[\Attribute(\Attribute::TARGET_PROPERTY)]
Expand Down
22 changes: 12 additions & 10 deletions src/Contract/Attributes/MapFrom.php
Original file line number Diff line number Diff line change
@@ -1,49 +1,51 @@
<?php

declare(strict_types=1);

namespace Backbrain\Automapper\Contract\Attributes;

use Backbrain\Automapper\Contract\ValueResolverInterface;
use Symfony\Component\ExpressionLanguage\Expression;

#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class MapFrom
{
private string $source;

private ValueResolverInterface|string $mapFrom;
private Expression|ValueResolverInterface $valueResolverOrExpression;

/**
* Configure the mapping for a specific member.
* The argument `mapFrom` can be Symfony Expression Language expressions.
* <code>
* // using Symfony Expression Language
* // see https://symfony.com/doc/current/reference/formats/expression_language.html
* use Symfony\Component\ExpressionLanguage\Expression;
* class AccountDTO {
* #[ForMember(ProfileDTO::class,
* mapFrom: 'source.givenName~" "~source.givenName',
* )]
* #[MapFrom(ProfileDTO::class, 'source.givenName~" "~source.givenName')]
* public string $displayName;
* }
* </code>
* Within the expression you can use the following variables:
* - `source`: the source object
* - `context`: the current `Backbrain\Automapper\Contracts\ResolutionContextInterface`.
*
* @param string $source the source member type for which this member configuration is applied
* @param ValueResolverInterface|string $mapFrom it takes a ValueResolverInterface or a valid Symfony EL expression that will be used resolve the member value
* @param string $source the source member type for which this member configuration is applied
* @param ValueResolverInterface|Expression|string $valueResolverOrExpression Can be an instance of ValueResolverInterface or a Symfony EL expression that will be used resolve the member value
*/
public function __construct(string $source, ValueResolverInterface|string $mapFrom)
public function __construct(string $source, string|Expression|ValueResolverInterface $valueResolverOrExpression)
{
$this->source = $source;
$this->mapFrom = $mapFrom;
$this->valueResolverOrExpression = is_string($valueResolverOrExpression) ? new Expression($valueResolverOrExpression) : $valueResolverOrExpression;
}

public function getSource(): string
{
return $this->source;
}

public function getMapFrom(): ValueResolverInterface|string
public function getValueResolverOrExpression(): Expression|ValueResolverInterface
{
return $this->mapFrom;
return $this->valueResolverOrExpression;
}
}
Loading

0 comments on commit e9bb44e

Please sign in to comment.