Skip to content
Merged
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
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
Loading