Skip to content

Commit

Permalink
feat: extra target values (#229)
Browse files Browse the repository at this point in the history
* feat: extra target values

* optimization

* process properties

* fix error

* changelog

* handle invalid extra target properties
  • Loading branch information
priyadi authored Oct 13, 2024
1 parent feed0d4 commit 10e5848
Show file tree
Hide file tree
Showing 13 changed files with 386 additions and 27 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CHANGELOG

## 1.10.1
## 1.11.0

* refactor: spin off `resolveTargetClass()` to separate class
* perf: proxy warming
Expand All @@ -11,6 +11,7 @@
* perf: `ObjectToObjectTransformer` optimization
* perf: move `TypeResolver` outside of the hot path
* perf(`ObjectCache`): use sentinel value instead of exception
* feat: extra target values

## 1.10.0

Expand Down
44 changes: 44 additions & 0 deletions src/Context/ExtraTargetValues.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Context;

/**
* Adds additional values to the target object
*/
final readonly class ExtraTargetValues
{
/**
* @param array<class-string,array<string,mixed>> $arguments
*/
public function __construct(
private array $arguments = [],
) {}

/**
* @param list<class-string> $classes The class and its parent classes and interfaces.
* @return array<string,mixed>
*/
public function getArgumentsForClass(array $classes): array
{
$arguments = [];

foreach ($classes as $class) {
if (isset($this->arguments[$class])) {
$arguments = array_merge($arguments, $this->arguments[$class]);
}
}

return $arguments;
}
}
40 changes: 40 additions & 0 deletions src/Transformer/Exception/ExtraTargetPropertyNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Transformer\Exception;

use Rekalogika\Mapper\Context\Context;
use Rekalogika\Mapper\Exception\LogicException;

/**
* @internal
*/
class ExtraTargetPropertyNotFoundException extends LogicException
{
/**
* @param class-string $class
*/
public function __construct(
string $class,
string $property,
Context $context,
) {
$message = \sprintf(
'Mapper is called with "ExtraTargetValues", but cannot find the target property "%s" in class "%s"',
$property,
$class,
);

parent::__construct($message, context: $context);
}
}
111 changes: 105 additions & 6 deletions src/Transformer/Implementation/ObjectToObjectTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Rekalogika\Mapper\CacheWarmer\WarmableObjectToObjectMetadataFactoryInterface;
use Rekalogika\Mapper\CacheWarmer\WarmableTransformerInterface;
use Rekalogika\Mapper\Context\Context;
use Rekalogika\Mapper\Context\ExtraTargetValues;
use Rekalogika\Mapper\Context\MapperOptions;
use Rekalogika\Mapper\Exception\InvalidArgumentException;
use Rekalogika\Mapper\ObjectCache\ObjectCache;
Expand All @@ -26,6 +27,7 @@
use Rekalogika\Mapper\ServiceMethod\ServiceMethodSpecification;
use Rekalogika\Mapper\SubMapper\SubMapperFactoryInterface;
use Rekalogika\Mapper\Transformer\Exception\ClassNotInstantiableException;
use Rekalogika\Mapper\Transformer\Exception\ExtraTargetPropertyNotFoundException;
use Rekalogika\Mapper\Transformer\Exception\InstantiationFailureException;
use Rekalogika\Mapper\Transformer\Exception\NotAClassException;
use Rekalogika\Mapper\Transformer\Exception\UninitializedSourcePropertyException;
Expand Down Expand Up @@ -123,6 +125,13 @@ public function transform(
$target = null;
}

// get extra target values

$extraTargetValues = $this->getExtraTargetValues(
objectToObjectMetadata: $objectToObjectMetadata,
context: $context,
);

// initialize target if target is null

if (null === $target) {
Expand All @@ -133,12 +142,14 @@ public function transform(
$target = $this->instantiateTargetProxy(
source: $source,
objectToObjectMetadata: $objectToObjectMetadata,
extraTargetValues: $extraTargetValues,
context: $context,
);
} else {
$target = $this->instantiateRealTarget(
source: $source,
objectToObjectMetadata: $objectToObjectMetadata,
extraTargetValues: $extraTargetValues,
context: $context,
);
}
Expand Down Expand Up @@ -180,16 +191,47 @@ public function transform(
source: $source,
target: $target,
propertyMappings: $objectToObjectMetadata->getPropertyMappings(),
extraTargetValues: $extraTargetValues,
context: $context,
);
}

return $target;
}

/**
* @return array<string,mixed>
*/
private function getExtraTargetValues(
ObjectToObjectMetadata $objectToObjectMetadata,
Context $context,
): array {
$extraTargetValues = $context(ExtraTargetValues::class)
?->getArgumentsForClass($objectToObjectMetadata->getAllTargetClasses())
?? [];

$allPropertyMappings = $objectToObjectMetadata->getPropertyMappings();

foreach (array_keys($extraTargetValues) as $property) {
if (!isset($allPropertyMappings[$property])) {
throw new ExtraTargetPropertyNotFoundException(
class: $objectToObjectMetadata->getTargetClass(),
property: $property,
context: $context,
);
}
}

return $extraTargetValues;
}

/**
* @param array<string,mixed> $extraTargetValues
*/
private function instantiateRealTarget(
object $source,
ObjectToObjectMetadata $objectToObjectMetadata,
array $extraTargetValues,
Context $context,
): object {
$targetClass = $objectToObjectMetadata->getTargetClass();
Expand All @@ -203,6 +245,7 @@ private function instantiateRealTarget(
$constructorArguments = $this->generateConstructorArguments(
source: $source,
objectToObjectMetadata: $objectToObjectMetadata,
extraTargetValues: $extraTargetValues,
context: $context,
);

Expand All @@ -223,9 +266,13 @@ private function instantiateRealTarget(
}
}

/**
* @param array<string,mixed> $extraTargetValues
*/
private function instantiateTargetProxy(
object $source,
ObjectToObjectMetadata $objectToObjectMetadata,
array $extraTargetValues,
Context $context,
): object {
$targetClass = $objectToObjectMetadata->getTargetClass();
Expand All @@ -243,6 +290,7 @@ private function instantiateTargetProxy(
$source,
$objectToObjectMetadata,
$context,
$extraTargetValues,
): void {
// if the constructor is lazy, run it here

Expand All @@ -251,6 +299,7 @@ private function instantiateTargetProxy(
source: $source,
target: $target,
objectToObjectMetadata: $objectToObjectMetadata,
extraTargetValues: $extraTargetValues,
context: $context,
);
}
Expand All @@ -261,6 +310,7 @@ private function instantiateTargetProxy(
source: $source,
target: $target,
propertyMappings: $objectToObjectMetadata->getLazyPropertyMappings(),
extraTargetValues: $extraTargetValues,
context: $context,
);
};
Expand All @@ -280,6 +330,7 @@ private function instantiateTargetProxy(
source: $source,
target: $target,
objectToObjectMetadata: $objectToObjectMetadata,
extraTargetValues: $extraTargetValues,
context: $context,
);
}
Expand All @@ -290,16 +341,21 @@ private function instantiateTargetProxy(
source: $source,
target: $target,
propertyMappings: $objectToObjectMetadata->getEagerPropertyMappings(),
extraTargetValues: $extraTargetValues,
context: $context,
);

return $target;
}

/**
* @param array<string,mixed> $extraTargetValues
*/
private function runConstructorManually(
object $source,
object $target,
ObjectToObjectMetadata $objectToObjectMetadata,
array $extraTargetValues,
Context $context,
): object {
if (!method_exists($target, '__construct')) {
Expand All @@ -309,6 +365,7 @@ private function runConstructorManually(
$constructorArguments = $this->generateConstructorArguments(
source: $source,
objectToObjectMetadata: $objectToObjectMetadata,
extraTargetValues: $extraTargetValues,
context: $context,
);

Expand All @@ -334,16 +391,22 @@ private function runConstructorManually(
return $target;
}

/**
* @param array<string,mixed> $extraTargetValues
*/
private function generateConstructorArguments(
object $source,
ObjectToObjectMetadata $objectToObjectMetadata,
array $extraTargetValues,
Context $context,
): ConstructorArguments {
$propertyMappings = $objectToObjectMetadata->getConstructorPropertyMappings();
$constructorPropertyMappings = $objectToObjectMetadata->getConstructorPropertyMappings();

$constructorArguments = new ConstructorArguments();

foreach ($propertyMappings as $propertyMapping) {
// add arguments from property mappings

foreach ($constructorPropertyMappings as $propertyMapping) {
try {
/** @var mixed $targetPropertyValue */
[$targetPropertyValue,] = $this->transformValue(
Expand Down Expand Up @@ -379,16 +442,30 @@ private function generateConstructorArguments(
}
}

// add arguments from extra target values

/** @var mixed $value */
foreach ($extraTargetValues as $property => $value) {
// skip if there is no constructor property mapping for this
if (!isset($constructorPropertyMappings[$property])) {
continue;
}

$constructorArguments->addArgument($property, $value);
}

return $constructorArguments;
}

/**
* @param array<int,PropertyMapping> $propertyMappings
* @param array<string,PropertyMapping> $propertyMappings
* @param array<string,mixed> $extraTargetValues
*/
private function readSourceAndWriteTarget(
object $source,
object $target,
array $propertyMappings,
array $extraTargetValues,
Context $context,
): object {
foreach ($propertyMappings as $propertyMapping) {
Expand All @@ -400,6 +477,25 @@ private function readSourceAndWriteTarget(
);
}

// process extra target values

/** @var mixed $value */
foreach ($extraTargetValues as $property => $value) {
if (!isset($propertyMappings[$property])) {
continue;
}

$propertyMapping = $propertyMappings[$property];

$target = $this->readerWriter->writeTargetProperty(
target: $target,
propertyMapping: $propertyMapping,
value: $value,
context: $context,
silentOnError: true,
);
}

return $target;
}

Expand Down Expand Up @@ -435,7 +531,10 @@ private function readSourcePropertyAndWriteTargetProperty(

// write

if ($isChanged) {
if (
$isChanged
|| $propertyMapping->getTargetSetterWriteMode() === WriteMode::DynamicProperty
) {
if ($targetPropertyValue instanceof AdderRemoverProxy) {
$target = $targetPropertyValue->getHostObject();
}
Expand All @@ -445,6 +544,7 @@ private function readSourcePropertyAndWriteTargetProperty(
propertyMapping: $propertyMapping,
value: $targetPropertyValue,
context: $context,
silentOnError: false,
);
}

Expand Down Expand Up @@ -611,8 +711,7 @@ private function transformValue(

return [
$targetPropertyValue,
$targetPropertyValue !== $originalTargetPropertyValue
|| $propertyMapping->getTargetSetterWriteMode() === WriteMode::DynamicProperty,
$targetPropertyValue !== $originalTargetPropertyValue,
];
}

Expand Down
Loading

0 comments on commit 10e5848

Please sign in to comment.