Skip to content

Commit

Permalink
feat: Data collector.
Browse files Browse the repository at this point in the history
  • Loading branch information
priyadi committed Feb 5, 2024
1 parent 83a6812 commit 0162a59
Show file tree
Hide file tree
Showing 18 changed files with 479 additions and 15 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 0.6.2

* feat: Data collector.

## 0.6.1

* fix: Add caching for `ObjectMapperTable`.
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ psalm:
.PHONY: phpunit
phpunit:
$(eval c ?=)
rm -rf var
vendor/bin/phpunit --testdox -v $(c)

.PHONY: php-cs-fixer
Expand Down
25 changes: 25 additions & 0 deletions config/debug.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?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.
*/

use Rekalogika\Mapper\Debug\MapperDataCollector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();

$services
->set('rekalogika.mapper.data_collector', MapperDataCollector::class)
->tag('data_collector', [
'id' => 'rekalogika_mapper',
]);
};
5 changes: 4 additions & 1 deletion config/tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@
$services->set(PropertyMapperWithClassAttributeWithoutExplicitProperty::class);
$services->set(PropertyMapperWithExtraArguments::class);
$services->set(MoneyObjectMapper::class);
$services->set(MoneyToMoneyDtoTransformer::class);

$services->set(MoneyToMoneyDtoTransformer::class)
->tag('rekalogika.mapper.transformer');
$services->set(OverrideTransformer::class)
->tag('rekalogika.mapper.transformer')
->args([
'$transformer' => service(ScalarToScalarTransformer::class),
]);
Expand Down
73 changes: 73 additions & 0 deletions src/Debug/MapperDataCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?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\Debug;

use Rekalogika\Mapper\Transformer\Contracts\TransformerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\PropertyInfo\Type;

final class MapperDataCollector extends DataCollector
{
public function getName()
{
return 'rekalogika_mapper';
}

public function collect(
Request $request,
Response $response,
?\Throwable $exception = null
) {
}

/**
* @param class-string<TransformerInterface> $transformerClass
*/
public function createTraceData(
?string $path,
mixed $source,
mixed $target,
?Type $sourceType,
?Type $targetType,
string $transformerClass
): TraceData {
$traceData = new TraceData(
$path,
$this->cloneVar($source),
$this->cloneVar($target),
$sourceType,
$targetType,
$transformerClass
);

return $traceData;
}

public function collectTraceData(TraceData $traceData): void
{
/** @psalm-suppress MixedArrayAssignment */
$this->data['mappings'][] = $traceData;
}

/**
* @return array<int,TraceData>
*/
public function getMappings(): array
{
/** @var array<int,TraceData> */
return $this->data['mappings'];
}
}
110 changes: 110 additions & 0 deletions src/Debug/TraceData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?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\Debug;

use Rekalogika\Mapper\Exception\LogicException;
use Rekalogika\Mapper\Transformer\Contracts\TransformerInterface;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\VarDumper\Cloner\Data;

final class TraceData
{
private ?float $time = null;

/** @var array<int,self> */
private array $nestedTraceData = [];

/**
* @param class-string<TransformerInterface> $transformerClass
*/
public function __construct(
private ?string $path,
private Data $source,
private Data $target,
private ?Type $sourceType,
private ?Type $targetType,
private string $transformerClass,
) {
}

public function getSourceType(): ?Type
{
return $this->sourceType;
}

public function getTargetType(): ?Type
{
return $this->targetType;
}

public function finalizeTime(float $time): self
{
if (count($this->nestedTraceData) === 0) {
// If this is the last trace data (no nested trace data)
$this->time = $time;
} else {
// If this is not the last trace data (has nested trace data), we
// don't use the given time, but we calculate the time from the
// nested trace data
$this->time = array_sum(array_map(fn (self $traceData) => $traceData->getTime(), $this->nestedTraceData));
}

return $this;
}

public function getTime(): float
{
if ($this->time === null) {
throw new LogicException('Time is not set');
}

return $this->time;
}

/**
* @return class-string<TransformerInterface>
*/
public function getTransformerClass(): string
{
return $this->transformerClass;
}

/**
* @return array<int,self>
*/
public function getNestedTraceData(): array
{
return $this->nestedTraceData;
}

public function addNestedTraceData(self $traceData): void
{
$this->nestedTraceData[] = $traceData;
}

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

public function getTarget(): Data
{
return $this->target;
}

public function getPath(): ?string
{
return $this->path;
}
}
107 changes: 107 additions & 0 deletions src/Debug/TraceableTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?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\Debug;

use Rekalogika\Mapper\Context\Context;
use Rekalogika\Mapper\Context\ContextMemberNotFoundException;
use Rekalogika\Mapper\MainTransformer\MainTransformerInterface;
use Rekalogika\Mapper\MainTransformer\Model\Path;
use Rekalogika\Mapper\Transformer\Contracts\MainTransformerAwareInterface;
use Rekalogika\Mapper\Transformer\Contracts\MainTransformerAwareTrait;
use Rekalogika\Mapper\Transformer\Contracts\TransformerInterface;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\VarDumper\Cloner\Data;

final class TraceableTransformer implements
TransformerInterface,
MainTransformerAwareInterface
{
use MainTransformerAwareTrait;

private bool $withMainTransformerCalled = false;

public function __construct(
private TransformerInterface $decorated,
private MapperDataCollector $dataCollector
) {
}

public function getDecorated(): TransformerInterface
{
return $this->decorated;
}

public function withMainTransformer(MainTransformerInterface $mainTransformer): static
{
if ($this->withMainTransformerCalled) {
return $this;
}

$this->withMainTransformerCalled = true;

if ($this->decorated instanceof MainTransformerAwareInterface) {
$this->decorated = $this->decorated->withMainTransformer($mainTransformer);
}

return $this;
}

public function transform(
mixed $source,
mixed $target,
?Type $sourceType,
?Type $targetType,
Context $context
): mixed {
try {
$path = $context(Path::class)->getLast();
} catch (ContextMemberNotFoundException) {
$path = null;
}

$traceData = $this->dataCollector->createTraceData(
$path,
$source,
$target,
$sourceType,
$targetType,
$this->decorated::class
);

try {
// add trace data to parent trace data
$context(TraceData::class)
->addNestedTraceData($traceData);
} catch (ContextMemberNotFoundException) {
// 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;

$traceData->finalizeTime($time);

return $result;
}

public function getSupportedTransformation(): iterable
{
return $this->decorated->getSupportedTransformation();
}
}
45 changes: 45 additions & 0 deletions src/DependencyInjection/CompilerPass/DebugTransformerPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?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\DependencyInjection\CompilerPass;

use Rekalogika\Mapper\Debug\TraceableTransformer;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

final class DebugTransformerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$taggedServices = $container->findTaggedServiceIds('rekalogika.mapper.transformer');
$dataCollector = new Reference('rekalogika.mapper.data_collector');

foreach ($taggedServices as $serviceId => $tags) {
$decoratedServiceId = 'debug.' . $serviceId;

$service = $container->getDefinition($serviceId);
/** @var array<string,mixed> */
$tagAttributes = $service->getTag('rekalogika.mapper.transformer')[0] ?? [];
$service->clearTag('rekalogika.mapper.transformer');

$container->register($decoratedServiceId, TraceableTransformer::class)
->setDecoratedService($serviceId)
->addTag('rekalogika.mapper.transformer', $tagAttributes)
->setArguments([
$service,
$dataCollector,
]);
}
}
}
Loading

0 comments on commit 0162a59

Please sign in to comment.