Skip to content

Commit

Permalink
feat: PresetTransformer
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi committed Feb 19, 2024
1 parent eafa73f commit b0eecc6
Show file tree
Hide file tree
Showing 14 changed files with 370 additions and 28 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# CHANGELOG

## 1.1.0

* feat: `PresetTransformer`.

## 1.0.0

* No changes.

## 0.10.2

* fix: Handle cases where transformed key is different from the original.
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ a few keystrokes.
* Improve non-framework usage.
* Warm up proxies on build time from the list of classes provided by the user.
* Lazy-loading using Doctrine `Collection` type hint on the target side.
* `PresetTransformer`. Transforms objects using a predetermined table of values.

## Documentation

Expand Down
23 changes: 14 additions & 9 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
use Rekalogika\Mapper\Transformer\Implementation\ObjectToArrayTransformer;
use Rekalogika\Mapper\Transformer\Implementation\ObjectToObjectTransformer;
use Rekalogika\Mapper\Transformer\Implementation\ObjectToStringTransformer;
use Rekalogika\Mapper\Transformer\Implementation\PresetTransformer;
use Rekalogika\Mapper\Transformer\Implementation\ScalarToScalarTransformer;
use Rekalogika\Mapper\Transformer\Implementation\StringToBackedEnumTransformer;
use Rekalogika\Mapper\Transformer\Implementation\SymfonyUidTransformer;
Expand Down Expand Up @@ -120,7 +121,7 @@

$services
->set(ScalarToScalarTransformer::class)
->tag('rekalogika.mapper.transformer', ['priority' => -350]);
->tag('rekalogika.mapper.transformer', ['priority' => -300]);

$services
->set(ObjectMapperTransformer::class)
Expand All @@ -130,29 +131,33 @@
service('rekalogika.mapper.object_mapper.table_factory'),
service('rekalogika.mapper.object_mapper.resolver'),
])
->tag('rekalogika.mapper.transformer', ['priority' => -400]);
->tag('rekalogika.mapper.transformer', ['priority' => -350]);

$services
->set(DateTimeTransformer::class)
->tag('rekalogika.mapper.transformer', ['priority' => -450]);
->tag('rekalogika.mapper.transformer', ['priority' => -400]);

$services
->set(StringToBackedEnumTransformer::class)
->tag('rekalogika.mapper.transformer', ['priority' => -450]);

$services
->set(SymfonyUidTransformer::class)
->tag('rekalogika.mapper.transformer', ['priority' => -500]);

$services
->set(ClassMethodTransformer::class)
->args([
service('rekalogika.mapper.sub_mapper.factory'),
])
->set(ObjectToStringTransformer::class)
->tag('rekalogika.mapper.transformer', ['priority' => -550]);

$services
->set(SymfonyUidTransformer::class)
->set(PresetTransformer::class)
->tag('rekalogika.mapper.transformer', ['priority' => -600]);

$services
->set(ObjectToStringTransformer::class)
->set(ClassMethodTransformer::class)
->args([
service('rekalogika.mapper.sub_mapper.factory'),
])
->tag('rekalogika.mapper.transformer', ['priority' => -650]);

$services
Expand Down
33 changes: 30 additions & 3 deletions src/Debug/TraceData.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ final class TraceData
private ?string $callerClass = null;
private ?string $callerType = null;
private ?string $callerName = null;
private bool $refused = false;

/**
* @param null|array<int,Type|MixedType> $possibleTargetTypes
Expand All @@ -57,7 +58,18 @@ public function __construct(
$this->existingTargetType = \get_debug_type($existingTargetValue);
}

public function finalizeTime(float $time): void
public function refusedToTransform(): void
{
$this->refused = true;
}

public function finalize(float $time, mixed $result): void
{
$this->finalizeTime($time);
$this->finalizeResult($result);
}

private function finalizeTime(float $time): void
{
if (count($this->nestedTraceData) === 0) {
// If this is the last trace data (no nested trace data)
Expand All @@ -70,7 +82,7 @@ public function finalizeTime(float $time): void
}
}

public function finalizeResult(mixed $result): void
private function finalizeResult(mixed $result): void
{
$this->resultType = \get_debug_type($result);
}
Expand All @@ -96,6 +108,14 @@ public function getNestedTraceData(): array
return $this->nestedTraceData;
}

/**
* @return array<int,self>
*/
public function getAcceptedNestedTraceData(): array
{
return array_filter($this->nestedTraceData, fn (self $traceData) => !$traceData->isRefused());
}

public function addNestedTraceData(self $traceData): void
{
$this->nestedTraceData[] = $traceData;
Expand Down Expand Up @@ -144,7 +164,6 @@ public function getSelectedTargetTypeHtml(): string
return TypeUtil::getTypeStringHtml($this->selectedTargetType);
}
return 'mixed';

}

public function getResultType(): string
Expand Down Expand Up @@ -227,4 +246,12 @@ public function getCaller(): ?array
'name' => $this->callerName,
];
}

/**
* Get the value of refused
*/
public function isRefused(): bool
{
return $this->refused;
}
}
27 changes: 17 additions & 10 deletions src/Debug/TraceableTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Rekalogika\Mapper\MainTransformer\Model\DebugContext;
use Rekalogika\Mapper\MainTransformer\Model\Path;
use Rekalogika\Mapper\Transformer\AbstractTransformerDecorator;
use Rekalogika\Mapper\Transformer\Exception\RefuseToTransformException;
use Rekalogika\Mapper\Transformer\MainTransformerAwareInterface;
use Rekalogika\Mapper\Transformer\MainTransformerAwareTrait;
use Rekalogika\Mapper\Transformer\TransformerInterface;
Expand Down Expand Up @@ -103,21 +104,27 @@ public function transform(
$caller['type'] ?? null
);

// if we are the root transformer, add the trace data to the
// context, and collect it
$context = $context->with($traceData);
$this->dataCollector->collectTraceData($traceData);
}

$start = microtime(true);
/** @var mixed */
$result = $this->decorated->transform($source, $target, $sourceType, $targetType, $context);
$time = microtime(true) - $start;
try {
$start = microtime(true);
/** @var mixed */
$result = $this->decorated->transform($source, $target, $sourceType, $targetType, $context);
$time = microtime(true) - $start;

$traceData->finalizeTime($time);
$traceData->finalizeResult($result);
$traceData->finalize($time, $result);

return $result;
if (!$parentTraceData) {
$this->dataCollector->collectTraceData($traceData);
}

return $result;
} catch (RefuseToTransformException $e) {
$traceData->refusedToTransform();

throw $e;
}
}

public function getSupportedTransformation(): iterable
Expand Down
4 changes: 4 additions & 0 deletions src/MainTransformer/Implementation/MainTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ public function transform(
context: $context
);
} catch (RefuseToTransformException) {
if ($targetTypeForTransformer !== null) {
$objectCache->undoPreCache($source, $targetTypeForTransformer);
}

continue;
}

Expand Down
25 changes: 25 additions & 0 deletions src/ObjectCache/ObjectCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,23 @@ public function preCache(mixed $source, Type $targetType): void
$this->preCache->offsetGet($source)?->offsetSet($targetTypeString, true);
}

public function undoPreCache(mixed $source, Type $targetType): void
{
if (!is_object($source)) {
return;
}

if ($this->isBlacklisted($source)) {
return;
}

$targetTypeString = $this->typeResolver->getTypeString($targetType);

if (isset($this->preCache[$source][$targetTypeString])) {
unset($this->preCache[$source][$targetTypeString]);
}
}

public function containsTarget(mixed $source, Type $targetType): bool
{
if (!is_object($source)) {
Expand Down Expand Up @@ -156,4 +173,12 @@ public function saveTarget(
unset($this->preCache[$source][$targetTypeString]);
}
}

/**
* @return \WeakMap<object,\ArrayObject<string,object>>
*/
public function getInternalMapping(): \WeakMap
{
return $this->cache;
}
}
117 changes: 117 additions & 0 deletions src/Transformer/Context/PresetMapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?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\Context;

use Rekalogika\Mapper\ObjectCache\ObjectCache;
use Rekalogika\Mapper\Transformer\Exception\PresetMappingNotFound;
use Rekalogika\Mapper\Transformer\Model\SplObjectStorageWrapper;

final readonly class PresetMapping
{
/**
* @var \WeakMap<object,\ArrayObject<class-string,object>>
*/
private \WeakMap $mappings;

/**
* @param iterable<object,iterable<class-string,object>> $mappings
*/
public function __construct(iterable $mappings)
{
/**
* @var \WeakMap<object,\ArrayObject<class-string,object>>
*/
$weakMap = new \WeakMap();

foreach ($mappings as $source => $classToTargetMapping) {
$classToTargetMappingArray = [];

foreach ($classToTargetMapping as $class => $target) {
$classToTargetMappingArray[$class] = $target;
}

$weakMap[$source] = new \ArrayObject($classToTargetMappingArray);
}

$this->mappings = $weakMap;
}

public static function fromObjectCache(ObjectCache $objectCache): self
{
$objectCacheWeakMap = $objectCache->getInternalMapping();

/** @var SplObjectStorageWrapper<object,\ArrayObject<class-string,object>> */
$presetMapping = new SplObjectStorageWrapper(new \SplObjectStorage());

/**
* @var object $source
* @var \ArrayObject<class-string,object> $classToTargetMapping
*/
foreach ($objectCacheWeakMap as $source => $classToTargetMapping) {
$newTargetClass = $source::class;
/** @var object */
$newTarget = $source;

/**
* @var string $targetClass
* @var object $target
*/
foreach ($classToTargetMapping as $targetClass => $target) {
if (!class_exists($targetClass)) {
continue;
}

$newSource = $target;

if (!$presetMapping->offsetExists($newSource)) {
/** @var \ArrayObject<class-string,object> */
$arrayObject = new \ArrayObject();
$presetMapping->offsetSet($newSource, $arrayObject);
}

$presetMapping->offsetGet($newSource)?->offsetSet($newTargetClass, $newTarget);
}
}

return new self($presetMapping);
}

/**
* @template T of object
* @param object $source
* @param class-string<T> $targetClass
* @return T
* @throws PresetMappingNotFound
*/
public function findResult(object $source, string $targetClass): object
{
$mappings = $this->mappings[$source] ?? null;

if (null === $mappings) {
throw new PresetMappingNotFound();
}

$result = $mappings[$targetClass] ?? null;

if (null === $result) {
throw new PresetMappingNotFound();
}

if (!($result instanceof $targetClass)) {
throw new PresetMappingNotFound();
}

return $result;
}
}
20 changes: 20 additions & 0 deletions src/Transformer/Exception/PresetMappingNotFound.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?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\Exception\RuntimeException;

class PresetMappingNotFound extends RuntimeException
{
}
Loading

0 comments on commit b0eecc6

Please sign in to comment.