Skip to content

Commit

Permalink
Optimize creation from constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
MrMeshok committed Sep 13, 2024
1 parent 76e25b5 commit 94377bf
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 60 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [GH#184](https://github.com/jolicode/automapper/pull/184) Fix error when mapping from stdClass to constructor with nullable/optional arguments
- [GH#185](https://github.com/jolicode/automapper/pull/185) Fix constructor with default parameter array does not work with constructor_arguments context

### Changed
- [GH#186](https://github.com/jolicode/automapper/pull/186) Optimize creation from constructor

## [9.1.2] - 2024-09-03
### Fixed
- [GH#174](https://github.com/jolicode/automapper/pull/174) Fix race condition when writing generated mappers
Expand Down
116 changes: 56 additions & 60 deletions src/Generator/CreateTargetStatementsGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,19 +126,19 @@ private function constructorArguments(GeneratorMetadata $metadata): array
// Find property for parameter
$propertyMetadata = $metadata->getTargetPropertyWithConstructor($constructorParameter->getName());

$createObjectStatement = null;
$propertyStatements = null;
$constructArgument = null;
$constructorName = null;

if (null !== $propertyMetadata) {
[$createObjectStatement, $constructArgument, $constructorName] = $this->constructorArgument($metadata, $propertyMetadata, $constructorParameter);
[$propertyStatements, $constructArgument, $constructorName] = $this->constructorArgument($metadata, $propertyMetadata, $constructorParameter);
}

if (null === $createObjectStatement || null === $constructArgument || null === $constructorName) {
[$createObjectStatement, $constructArgument, $constructorName] = $this->constructorArgumentWithoutSource($metadata, $constructorParameter);
if (null === $propertyStatements || null === $constructArgument || null === $constructorName) {
[$propertyStatements, $constructArgument, $constructorName] = $this->constructorArgumentWithoutSource($metadata, $constructorParameter);
}

$createObjectStatements[] = $createObjectStatement;
$createObjectStatements = [...$createObjectStatements, ...$propertyStatements];
$constructArguments[$constructorName] = $constructArgument;
}

Expand All @@ -158,17 +158,18 @@ private function constructorArguments(GeneratorMetadata $metadata): array
}

/**
* Check if there is a constructor argument in the context, otherwise we use the transformed value.
* If source missing a constructor argument, check if there is a constructor argument in the context, otherwise we use the default value or throw exception.
*
* ```php
* if (MapperContext::hasConstructorArgument($context, $target, 'propertyName')) {
* $constructArg1 = $source->propertyName ?? MapperContext::getConstructorArgument($context, $target, 'propertyName');
* } else {
* $constructArg1 = $source->propertyName;
* }
* {transformation of value}
* $constructarg = $value ?? (
* MapperContext::hasConstructorArgument($context, $target, 'propertyName')
* ? MapperContext::getConstructorArgument($context, $target, 'propertyName')
* : {defaultValueExpr} // default value or throw exception
* )
* ```
*
* @return array{Stmt, Arg, string}|array{null, null, null}
* @return array{Stmt[], Arg, string}|array{null, null, null}
*/
private function constructorArgument(GeneratorMetadata $metadata, PropertyMetadata $propertyMetadata, \ReflectionParameter $parameter): array
{
Expand Down Expand Up @@ -218,45 +219,39 @@ private function constructorArgument(GeneratorMetadata $metadata, PropertyMetada
}

return [
new Stmt\If_(new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'hasConstructorArgument', [
new Arg($variableRegistry->getContext()),
new Arg(new Scalar\String_($metadata->mapperMetadata->target)),
new Arg(new Scalar\String_($propertyMetadata->target->property)),
]), [
'stmts' => [
...$propStatements,
new Stmt\Expression($argumentAssignClosure(new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'getConstructorArgument', [
new Arg($variableRegistry->getContext()),
new Arg(new Scalar\String_($metadata->mapperMetadata->target)),
new Arg(new Scalar\String_($propertyMetadata->target->property)),
]))),
],
'else' => new Stmt\Else_([
...$propStatements,
new Stmt\Expression($argumentAssignClosure($defaultValueExpr)),
]),
]),
[
...$propStatements,
new Stmt\Expression($argumentAssignClosure(
new Expr\Ternary(
new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'hasConstructorArgument', [
new Arg($variableRegistry->getContext()),
new Arg(new Scalar\String_($metadata->mapperMetadata->target)),
new Arg(new Scalar\String_($propertyMetadata->target->property)),
]),
new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'getConstructorArgument', [
new Arg($variableRegistry->getContext()),
new Arg(new Scalar\String_($metadata->mapperMetadata->target)),
new Arg(new Scalar\String_($propertyMetadata->target->property)),
]),
$defaultValueExpr,
),
)),
],
new Arg($constructVar, name: new Identifier($parameter->getName())),
$parameter->getName(),
];
}

/**
* Check if there is a constructor argument in the context, otherwise we use the default value.
* Check if there is a constructor argument in the context, otherwise we use the default value or throw exception.
*
* ```
* if (MapperContext::hasConstructorArgument($context, $target, 'propertyName')) {
* $constructArg2 = MapperContext::getConstructorArgument($context, $target, 'propertyName');
* } else {
* $constructArg2 = 'default value';
* // or set to null if the parameter is nullable
* $constructArg2 = null;
* // throw an exception otherwise
* throw new MissingConstructorArgumentsException('Cannot create an instance of "Foo" from mapping data because its constructor requires the following parameters to be present : "$propertyName".', 0, null, ['propertyName'], 'Foo');
* }
* ```php
* $constructarg = MapperContext::hasConstructorArgument($context, $target, 'propertyName')
* ? MapperContext::getConstructorArgument($context, $target, 'propertyName')
* : {defaultValueExpr} // default value or throw exception
* ```
*
* @return array{Stmt, Arg, string}
* @return array{Stmt[], Arg, string}
*/
private function constructorArgumentWithoutSource(GeneratorMetadata $metadata, \ReflectionParameter $constructorParameter): array
{
Expand All @@ -274,28 +269,29 @@ private function constructorArgumentWithoutSource(GeneratorMetadata $metadata, \
]));

if ($constructorParameter->isDefaultValueAvailable()) {
$defaultValueExpr = new Expr\Assign($constructVar, $this->getValueAsExpr($constructorParameter->getDefaultValue()));
$defaultValueExpr = $this->getValueAsExpr($constructorParameter->getDefaultValue());
} elseif ($constructorParameter->allowsNull()) {
$defaultValueExpr = new Expr\Assign($constructVar, new Expr\ConstFetch(new Name('null')));
$defaultValueExpr = new Expr\ConstFetch(new Name('null'));
}

return [
new Stmt\If_(new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'hasConstructorArgument', [
new Arg($variableRegistry->getContext()),
new Arg(new Scalar\String_($metadata->mapperMetadata->target)),
new Arg(new Scalar\String_($constructorParameter->getName())),
]), [
'stmts' => [
new Stmt\Expression(new Expr\Assign($constructVar, new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'getConstructorArgument', [
new Arg($variableRegistry->getContext()),
new Arg(new Scalar\String_($metadata->mapperMetadata->target)),
new Arg(new Scalar\String_($constructorParameter->getName())),
]))),
],
'else' => new Stmt\Else_([
new Stmt\Expression($defaultValueExpr),
]),
]),
[
new Stmt\Expression(new Expr\Assign($constructVar,
new Expr\Ternary(
new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'hasConstructorArgument', [
new Arg($variableRegistry->getContext()),
new Arg(new Scalar\String_($metadata->mapperMetadata->target)),
new Arg(new Scalar\String_($constructorParameter->getName())),
]),
new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'getConstructorArgument', [
new Arg($variableRegistry->getContext()),
new Arg(new Scalar\String_($metadata->mapperMetadata->target)),
new Arg(new Scalar\String_($constructorParameter->getName())),
]),
$defaultValueExpr,
))
),
],
new Arg($constructVar, name: new Identifier($constructorParameter->getName())),
$constructorParameter->getName(),
];
Expand Down

0 comments on commit 94377bf

Please sign in to comment.