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
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
use ApiPlatform\Metadata\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;

/**
* Injects the metadata aware name converter if available.
* Creates API Platform's own metadata-aware name converter to avoid polluting Symfony's global serializer.
*
* @internal
*
Expand All @@ -36,17 +38,24 @@ final class MetadataAwareNameConverterPass implements CompilerPassInterface
*/
public function process(ContainerBuilder $container): void
{
if (!$container->hasDefinition('serializer.name_converter.metadata_aware')) {
if (!$container->hasDefinition('serializer.mapping.class_metadata_factory')) {
return;
}

if ($container->hasAlias('api_platform.name_converter')) {
$nameConverter = (string) $container->getAlias('api_platform.name_converter');
$fallbackConverter = null;

$container->setParameter('.serializer.name_converter', $nameConverter);
$container->getDefinition('serializer.name_converter.metadata_aware')->setArgument(1, new Reference($nameConverter));
// Check if user explicitly configured a name converter for API Platform
if ($container->hasAlias('api_platform.name_converter')) {
$fallbackConverter = new Reference((string) $container->getAlias('api_platform.name_converter'));
}

$container->setAlias('api_platform.name_converter', 'serializer.name_converter.metadata_aware');
// Create API Platform's own metadata-aware name converter (isolated from Symfony's)
$definition = new Definition(MetadataAwareNameConverter::class, [
new Reference('serializer.mapping.class_metadata_factory'),
$fallbackConverter,
]);

$container->setDefinition('api_platform.name_converter.metadata_aware', $definition);
$container->setAlias('api_platform.name_converter', 'api_platform.name_converter.metadata_aware');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,18 @@

use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\MetadataAwareNameConverterPass;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;

/**
* @author Antoine Bluchet <soyuka@gmail.com>
*/
class MetadataAwareNameConverterPassTest extends TestCase
{
use ProphecyTrait;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you plan to remove prophecy ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally yes at some point, whenever I add new tests I prefer avoiding it, I think I wrote that in the CONTRIBUTING.md file


public function testConstruct(): void
{
$this->assertInstanceOf(CompilerPassInterface::class, new MetadataAwareNameConverterPass());
Expand All @@ -38,42 +36,66 @@ public function testProcessWithNameConverter(): void
{
$pass = new MetadataAwareNameConverterPass();

$reference = new Reference('app.name_converter');
$container = new ContainerBuilder();
$container->setDefinition('serializer.mapping.class_metadata_factory', new Definition());
$container->setAlias('api_platform.name_converter', 'app.name_converter');

$pass->process($container);

$definition = $this->prophesize(Definition::class);
$definition->setArgument(1, $reference)->shouldBeCalled()->willReturn($definition);
// Should create API Platform's own metadata-aware converter
$this->assertTrue($container->hasDefinition('api_platform.name_converter.metadata_aware'));
$definition = $container->getDefinition('api_platform.name_converter.metadata_aware');
$this->assertSame(MetadataAwareNameConverter::class, $definition->getClass());

$containerBuilderProphecy = $this->prophesize(ContainerBuilder::class);
$containerBuilderProphecy->hasAlias('api_platform.name_converter')->willReturn(true)->shouldBeCalled();
$containerBuilderProphecy->getAlias('api_platform.name_converter')->shouldBeCalled()->willReturn(new Alias('app.name_converter'));
$containerBuilderProphecy->hasDefinition('serializer.name_converter.metadata_aware')->shouldBeCalled()->willReturn(true);
$containerBuilderProphecy->getDefinition('serializer.name_converter.metadata_aware')->shouldBeCalled()->willReturn($definition);
$containerBuilderProphecy->setAlias('api_platform.name_converter', 'serializer.name_converter.metadata_aware')->shouldBeCalled();
$containerBuilderProphecy->setParameter('.serializer.name_converter', 'app.name_converter')->shouldBeCalled();
// Should have class metadata factory as first argument
$args = $definition->getArguments();
$this->assertInstanceOf(Reference::class, $args[0]);
$this->assertSame('serializer.mapping.class_metadata_factory', (string) $args[0]);

$pass->process($containerBuilderProphecy->reveal());
// Should have the user's converter as fallback (second argument)
$this->assertInstanceOf(Reference::class, $args[1]);
$this->assertSame('app.name_converter', (string) $args[1]);

// Should alias api_platform.name_converter to the new service
$this->assertTrue($container->hasAlias('api_platform.name_converter'));
$alias = $container->getAlias('api_platform.name_converter');
$this->assertSame('api_platform.name_converter.metadata_aware', (string) $alias);
}

public function testProcessWithoutNameConverter(): void
{
$pass = new MetadataAwareNameConverterPass();

$containerBuilderProphecy = $this->prophesize(ContainerBuilder::class);
$containerBuilderProphecy->hasAlias('api_platform.name_converter')->willReturn(false)->shouldBeCalled();
$containerBuilderProphecy->hasDefinition('serializer.name_converter.metadata_aware')->shouldBeCalled()->willReturn(true);
$containerBuilderProphecy->setAlias('api_platform.name_converter', 'serializer.name_converter.metadata_aware')->shouldBeCalled();
$container = new ContainerBuilder();
$container->setDefinition('serializer.mapping.class_metadata_factory', new Definition());

$pass->process($container);

$pass->process($containerBuilderProphecy->reveal());
// Should still create API Platform's own metadata-aware converter (without fallback)
$this->assertTrue($container->hasDefinition('api_platform.name_converter.metadata_aware'));
$definition = $container->getDefinition('api_platform.name_converter.metadata_aware');
$this->assertSame(MetadataAwareNameConverter::class, $definition->getClass());

$args = $definition->getArguments();
$this->assertInstanceOf(Reference::class, $args[0]);
$this->assertSame('serializer.mapping.class_metadata_factory', (string) $args[0]);
$this->assertNull($args[1]); // No fallback converter

// Should alias api_platform.name_converter to the new service
$this->assertTrue($container->hasAlias('api_platform.name_converter'));
}

public function testProcessWithoutMetadataAwareDefinition(): void
public function testProcessWithoutClassMetadataFactory(): void
{
$pass = new MetadataAwareNameConverterPass();

$containerBuilderProphecy = $this->prophesize(ContainerBuilder::class);
$containerBuilderProphecy->hasDefinition('serializer.name_converter.metadata_aware')->willReturn(false)->shouldBeCalled();
$containerBuilderProphecy->setAlias('api_platform.name_converter', 'serializer.name_converter.metadata_aware')->shouldNotBeCalled();
$container = new ContainerBuilder();
// No serializer.mapping.class_metadata_factory defined

$pass->process($container);

$pass->process($containerBuilderProphecy->reveal());
// Should not create anything if class metadata factory is missing
$this->assertFalse($container->hasDefinition('api_platform.name_converter.metadata_aware'));
$this->assertFalse($container->hasAlias('api_platform.name_converter'));
}
}
Loading