Skip to content

Commit

Permalink
feat(debug): add debug command and profiler for symfony
Browse files Browse the repository at this point in the history
  • Loading branch information
joelwurtz committed Apr 2, 2024
1 parent 0fbe6af commit f101496
Show file tree
Hide file tree
Showing 31 changed files with 585 additions and 30 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"moneyphp/money": "^3.3.2",
"phpdocumentor/type-resolver": "^1.7",
"phpunit/phpunit": "^9.0",
"symfony/browser-kit": "^7.0",
"symfony/browser-kit": "^6.4 || ^7.0",
"symfony/console": "^6.4 || ^7.0",
"symfony/filesystem": "^6.4 || ^7.0",
"symfony/framework-bundle": "*",
"symfony/http-client": "^6.4 || ^7.0",
Expand Down
1 change: 1 addition & 0 deletions src/Event/PropertyMetadataEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public function __construct(
public ?int $maxDepth = null,
public ?TransformerInterface $transformer = null,
public ?bool $ignored = null,
public ?string $ignoreReason = null,
public ?string $if = null,
public ?array $groups = null,
public ?bool $disableGroupsCheck = null,
Expand Down
1 change: 1 addition & 0 deletions src/EventListener/MapFromListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ private function addPropertyFromTarget(GenerateMapperEvent $event, MapFrom $mapF
maxDepth: $mapFrom->maxDepth,
transformer: $this->getTransformerFromMapAttribute($event->mapperMetadata->target, $mapFrom, false),
ignored: $mapFrom->ignore,
ignoreReason: $mapFrom->ignore === true ? 'Property is ignored by MapFrom Attribute on Target' : null,
if: $mapFrom->if,
groups: $mapFrom->groups,
);
Expand Down
1 change: 1 addition & 0 deletions src/EventListener/MapToListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ private function addPropertyFromSource(GenerateMapperEvent $event, MapTo $mapTo,
maxDepth: $mapTo->maxDepth,
transformer: $this->getTransformerFromMapAttribute($event->mapperMetadata->source, $mapTo),
ignored: $mapTo->ignore,
ignoreReason: $mapTo->ignore === true ? 'Property is ignored by MapTo Attribute on Source' : null,
if: $mapTo->if,
groups: $mapTo->groups,
);
Expand Down
2 changes: 2 additions & 0 deletions src/EventListener/Symfony/SerializerIgnoreListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ public function __invoke(PropertyMetadataEvent $event): void

if (($event->mapperMetadata->source !== 'array' && $event->mapperMetadata->source !== \stdClass::class) && $this->isIgnoreProperty($event->mapperMetadata->source, $event->source->name)) {
$event->ignored = true;
$event->ignoreReason = 'Property is ignored by Symfony Serializer Attribute on Source';

return;
}

if (($event->mapperMetadata->target !== 'array' && $event->mapperMetadata->target !== \stdClass::class) && $this->isIgnoreProperty($event->mapperMetadata->target, $event->target->name)) {
$event->ignored = true;
$event->ignoreReason = 'Property is ignored by Symfony Serializer Attribute on Target';
}
}

Expand Down
1 change: 1 addition & 0 deletions src/Metadata/MapperMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class MapperMetadata
public function __construct(
public string $source,
public string $target,
public bool $registered,
private string $classPrefix = 'Mapper_',
) {
if (class_exists($this->source) && !\in_array($this->source, ['array', \stdClass::class], true)) {
Expand Down
30 changes: 28 additions & 2 deletions src/Metadata/MetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use AutoMapper\Extractor\ReadWriteTypeExtractor;
use AutoMapper\Extractor\SourceTargetMappingExtractor;
use AutoMapper\Extractor\WriteMutator;
use AutoMapper\Transformer\AllowNullValueTransformerInterface;
use AutoMapper\Transformer\ArrayTransformerFactory;
use AutoMapper\Transformer\BuiltinTransformerFactory;
use AutoMapper\Transformer\ChainTransformerFactory;
Expand All @@ -37,6 +38,7 @@
use AutoMapper\Transformer\SymfonyUidTransformerFactory;
use AutoMapper\Transformer\TransformerFactoryInterface;
use AutoMapper\Transformer\UniqueTypeTransformerFactory;
use AutoMapper\Transformer\VoidTransformer;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
Expand Down Expand Up @@ -118,6 +120,18 @@ public function resolveAllMetadata(MetadataRegistry $metadataRegistry): void
}
}

/**
* @return iterable<GeneratorMetadata>
*/
public function listMetadata(): iterable
{
foreach ($this->generatorMetadata as $targets) {
foreach ($targets as $metadata) {
yield $metadata;
}
}
}

private function createGeneratorMetadata(MapperMetadata $mapperMetadata): GeneratorMetadata
{
$extractor = $this->sourceTargetPropertiesMappingExtractor;
Expand Down Expand Up @@ -225,18 +239,30 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera
$transformer = $this->transformerFactory->getTransformer($propertyMappedEvent->types, $sourcePropertyMetadata, $targetPropertyMetadata, $mapperMetadata);

if (null === $transformer) {
continue;
$propertyMappedEvent->ignored = true;
$propertyMappedEvent->ignoreReason = 'We didn\'t find a way to correctly transform this property.';
}

$propertyMappedEvent->transformer = $transformer;
}

if ($sourcePropertyMetadata->accessor === null && !($propertyMappedEvent->transformer instanceof AllowNullValueTransformerInterface)) {
$propertyMappedEvent->ignored = true;
$propertyMappedEvent->ignoreReason = 'Property cannot be read from source, and the attached transformer require a value.';
}

if ($targetPropertyMetadata->writeMutator === null && $targetPropertyMetadata->parameterInConstructor === null) {
$propertyMappedEvent->ignored = true;
$propertyMappedEvent->ignoreReason = 'Property cannot be write on target.';
}

$propertiesMapping[] = new PropertyMetadata(
$sourcePropertyMetadata,
$targetPropertyMetadata,
$propertyMappedEvent->types,
$propertyMappedEvent->transformer,
$propertyMappedEvent->transformer ?? new VoidTransformer(),
$propertyMappedEvent->ignored ?? false,
$propertyMappedEvent->ignoreReason ?? '',
$propertyMappedEvent->maxDepth,
$propertyMappedEvent->if,
$propertyMappedEvent->groups,
Expand Down
18 changes: 13 additions & 5 deletions src/Metadata/MetadataRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ public function __construct(
* @param class-string<object>|'array' $source
* @param class-string<object>|'array' $target
*/
public function get(string $source, string $target): MapperMetadata
public function get(string $source, string $target, bool $registered = false): MapperMetadata
{
$source = $this->getRealClassName($source);
$target = $this->getRealClassName($target);

return $this->registry[$source][$target] ??= new MapperMetadata($source, $target, $this->configuration->classPrefix);
return $this->registry[$source][$target] ??= new MapperMetadata($source, $target, $registered, $this->configuration->classPrefix);
}

/**
Expand All @@ -43,19 +43,27 @@ public function get(string $source, string $target): MapperMetadata
*/
public function register(string $source, string $target): void
{
$this->get($source, $target);
$this->get($source, $target, true);
}

/**
* @param class-string<object>|'array' $source
* @param class-string<object>|'array' $target
*/
public function has(string $source, string $target): bool
public function has(string $source, string $target, bool $onlyRegistered): bool
{
$source = $this->getRealClassName($source);
$target = $this->getRealClassName($target);

return isset($this->registry[$source][$target]);
if (!isset($this->registry[$source][$target])) {
return false;
}

if ($onlyRegistered) {
return $this->registry[$source][$target]->registered;
}

return true;
}

public function getIterator(): \Traversable
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/PropertyMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public function __construct(
public readonly TypesMatching $types,
public TransformerInterface $transformer,
public bool $ignored = false,
public string $ignoreReason = '',
public ?int $maxDepth = null,
public ?string $if = null,
public ?array $groups = null,
Expand Down
8 changes: 4 additions & 4 deletions src/Normalizer/AutoMapperNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public function supportsNormalization(mixed $data, string $format = null, array
return true;
}

return $this->onlyMetadataRegistry->has($data::class, 'array');
return $this->onlyMetadataRegistry->has($data::class, 'array', true);
}

/**
Expand All @@ -112,7 +112,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma
return true;
}

return $this->onlyMetadataRegistry->has('array', $type);
return $this->onlyMetadataRegistry->has('array', $type, true);
}

public function getSupportedTypes(?string $format): array
Expand All @@ -125,12 +125,12 @@ public function getSupportedTypes(?string $format): array

foreach ($this->onlyMetadataRegistry as $metadata) {
if ($metadata->source === 'array') {
$hasTarget = $this->onlyMetadataRegistry->has($metadata->target, 'array');
$hasTarget = $this->onlyMetadataRegistry->has($metadata->target, 'array', true);

// Only cache when both source and target exist in the registry
$types[$metadata->target] = $hasTarget;
} elseif ($metadata->target === 'array') {
$hasSource = $this->onlyMetadataRegistry->has($metadata->target, 'array');
$hasSource = $this->onlyMetadataRegistry->has($metadata->target, 'array', true);

// Only cache when both source and target exist in the registry
$types[$metadata->source] = $hasSource;
Expand Down
103 changes: 103 additions & 0 deletions src/Symfony/Bundle/Command/DebugMapperCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

declare(strict_types=1);

namespace AutoMapper\Symfony\Bundle\Command;

use AutoMapper\Metadata\Dependency;
use AutoMapper\Metadata\MetadataFactory;
use AutoMapper\Metadata\PropertyMetadata;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

final class DebugMapperCommand extends Command
{
public function __construct(
private readonly MetadataFactory $metadataFactory
) {
parent::__construct('debug:mapper');
}

protected function configure(): void
{
$this
->setDescription('Debug Mapper from source to target')
->addArgument('source', InputArgument::REQUIRED, 'Source class or "array"')
->addArgument('target', InputArgument::REQUIRED, 'Target class or "array"')
;
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var class-string<object>|'array' $source */
$source = $input->getArgument('source');
/** @var class-string<object>|'array' $target */
$target = $input->getArgument('target');

$metadata = $this->metadataFactory->getGeneratorMetadata($source, $target);

$style = new SymfonyStyle($input, $output);
$style->section('Mapper:');

$style->horizontalTable(
['Source', 'Target', 'Classname', 'Check attributes', 'Use constructor', 'Provider'],
[
[
$metadata->mapperMetadata->source,
$metadata->mapperMetadata->target,
$metadata->mapperMetadata->className,
$metadata->checkAttributes ? 'Yes' : 'No',
$metadata->hasConstructor() ? 'Yes' : 'No',
$metadata->provider,
],
]);

$style->section('Dependencies:');

$style->table(
['Mapper', 'Source', 'Target'],
array_map(
fn (Dependency $dependency) => [
$dependency->mapperDependency->name,
$dependency->mapperDependency->source,
$dependency->mapperDependency->target,
],
$metadata->getDependencies()
)
);

$style->section('Used Properties:');

$style->table(
[sprintf('%s -> %s', $source, $target), 'If', 'Transformer', 'Groups', 'MaxDepth'],
array_map(
fn (PropertyMetadata $property) => [
$property->source->name . ' -> ' . $property->target->name,
$property->if,
\get_class($property->transformer),
$property->disableGroupsCheck ? 'Disabled' : implode(', ', $property->groups ?? []),
$property->maxDepth,
],
array_filter($metadata->propertiesMetadata, fn (PropertyMetadata $property) => !$property->ignored)
)
);

$style->section('Not Used Properties:');

$style->table(
[sprintf('%s -> %s', $source, $target), 'Not used reason'],
array_map(
fn (PropertyMetadata $property) => [
$property->source->name . ' -> ' . $property->target->name,
$property->ignoreReason,
],
array_filter($metadata->propertiesMetadata, fn (PropertyMetadata $property) => $property->ignored)
)
);

return Command::SUCCESS;
}
}
Loading

0 comments on commit f101496

Please sign in to comment.