Skip to content

Commit

Permalink
Refactor DoctrineDiscriminatorType
Browse files Browse the repository at this point in the history
  • Loading branch information
core23 committed Dec 6, 2021
1 parent a20f531 commit 460c59b
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 64 deletions.
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"php": "^8.0",
"ext-json": "*",
"ext-pcre": "*",
"sonata-project/doctrine-extensions": "^1.1",
"symfony/form": "^5.4 || ^6.0",
"symfony/http-foundation": "^5.4 || ^6.0",
"symfony/options-resolver": "^5.4 || ^6.0",
Expand Down
15 changes: 0 additions & 15 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,6 @@ parameters:
count: 1
path: src/Type/BatchTimeType.php

-
message: "#^Method Nucleos\\\\Form\\\\Type\\\\DoctrineDiscriminatorType\\:\\:__construct\\(\\) has parameter \\$manager with generic class Sonata\\\\Doctrine\\\\Entity\\\\BaseEntityManager but does not specify its types\\: T$#"
count: 1
path: src/Type/DoctrineDiscriminatorType.php

-
message: "#^Property Nucleos\\\\Form\\\\Type\\\\DoctrineDiscriminatorType\\:\\:\\$entityManager with generic class Sonata\\\\Doctrine\\\\Entity\\\\BaseEntityManager does not specify its types\\: T$#"
count: 1
path: src/Type/DoctrineDiscriminatorType.php

-
message: "#^Method Nucleos\\\\Form\\\\Tests\\\\Bridge\\\\Symfony\\\\App\\\\AppKernel\\:\\:configureContainer\\(\\) has parameter \\$container with no type specified\\.$#"
count: 1
Expand Down Expand Up @@ -50,11 +40,6 @@ parameters:
count: 1
path: tests/Bridge/Symfony/Bundle/BundleIntegrationTest.php

-
message: "#^Property Nucleos\\\\Form\\\\Tests\\\\Type\\\\DoctrineDiscriminatorTypeTest\\:\\:\\$baseEntityManager with generic class Sonata\\\\Doctrine\\\\Entity\\\\BaseEntityManager does not specify its types\\: T$#"
count: 1
path: tests/Type/DoctrineDiscriminatorTypeTest.php

-
message: "#^Property Nucleos\\\\Form\\\\Tests\\\\Type\\\\DoctrineDiscriminatorTypeTest\\:\\:\\$classMetadata with generic class Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata does not specify its types\\: T$#"
count: 1
Expand Down
7 changes: 7 additions & 0 deletions src/Bridge/Symfony/Resources/config/form.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Nucleos\Form\Type\BatchTimeType;
use Nucleos\Form\Type\DACHCountryType;
use Nucleos\Form\Type\DateOutputType;
use Nucleos\Form\Type\DoctrineDiscriminatorType;
use Nucleos\Form\Type\GenderType;
use Nucleos\Form\Type\NumberOutputType;
use Nucleos\Form\Type\OutputType;
Expand Down Expand Up @@ -60,6 +61,12 @@
'form.type' => 'nucleos_type_time_picker',
])

->set(DoctrineDiscriminatorType::class)
->tag('form.type', [])
->args([
service('doctrine'),
])

->set('nucleos_form.image_type_extension', ImageTypeExtension::class)
->tag('form.type_extension', [
'alias' => 'file',
Expand Down
48 changes: 38 additions & 10 deletions src/Type/DoctrineDiscriminatorType.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,22 @@

namespace Nucleos\Form\Type;

use Sonata\Doctrine\Entity\BaseEntityManager;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

abstract class DoctrineDiscriminatorType extends AbstractType
final class DoctrineDiscriminatorType extends AbstractType
{
private BaseEntityManager $entityManager;
private ManagerRegistry $manager;

public function __construct(BaseEntityManager $manager)
public function __construct(ManagerRegistry $manager)
{
$this->entityManager = $manager;
$this->manager = $manager;
}

public function getParent(): ?string
Expand All @@ -31,19 +35,43 @@ public function getParent(): ?string
}

public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'choice_loader' => function (Options $options): ChoiceLoaderInterface {
$class = $options['class'];

return new CallbackChoiceLoader(fn (): array => $this->getChoices($class));
},
]);

$resolver->setRequired(['class']);
}

/**
* @param class-string $class
*
* @return array<string, string>
*/
private function getChoices(string $class): array
{
$choices = [];

$meta = $this->entityManager->getEntityManager()->getClassMetadata($this->entityManager->getClass());
$manager = $this->manager->getManagerForClass($class);

if (null === $manager) {
return $choices;
}

$meta = $manager->getClassMetadata($class);

\assert($meta instanceof ClassMetadataInfo);

if (\is_array($meta->discriminatorMap)) {
foreach ($meta->discriminatorMap as $key => $class) {
foreach ($meta->discriminatorMap as $key => $value) {
$choices[$key] = $key;
}
}

$resolver->setDefaults([
'choices' => $choices,
]);
return $choices;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Nucleos\Form\Type\BatchTimeType;
use Nucleos\Form\Type\DACHCountryType;
use Nucleos\Form\Type\DateOutputType;
use Nucleos\Form\Type\DoctrineDiscriminatorType;
use Nucleos\Form\Type\GenderType;
use Nucleos\Form\Type\NumberOutputType;
use Nucleos\Form\Type\OutputType;
Expand All @@ -40,6 +41,7 @@ public function testLoadDefault(): void
$this->assertContainerBuilderHasService('nucleos_form.type.number_output', NumberOutputType::class);
$this->assertContainerBuilderHasService('nucleos_form.type.batch_time', BatchTimeType::class);
$this->assertContainerBuilderHasService('nucleos_form.type.time_picker', TimePickerType::class);
$this->assertContainerBuilderHasService(DoctrineDiscriminatorType::class);
$this->assertContainerBuilderHasService('nucleos_form.image_type_extension', ImageTypeExtension::class);
$this->assertContainerBuilderHasService('nucleos_form.download_type_extension', DownloadTypeExtension::class);

Expand Down
18 changes: 0 additions & 18 deletions tests/Fixtures/EntityDoctrineDiscriminatorType.php

This file was deleted.

99 changes: 79 additions & 20 deletions tests/Type/DoctrineDiscriminatorTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,28 @@

namespace Nucleos\Form\Tests\Type;

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Nucleos\Form\Tests\Fixtures\EntityDoctrineDiscriminatorType;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Nucleos\Form\Type\DoctrineDiscriminatorType;
use PHPUnit\Framework\MockObject\MockObject;
use Sonata\Doctrine\Entity\BaseEntityManager;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormTypeInterface;

final class DoctrineDiscriminatorTypeTest extends BaseTypeTest
{
/**
* @var BaseEntityManager&MockObject
* @var ManagerRegistry&MockObject
*/
private BaseEntityManager $baseEntityManager;
private ManagerRegistry $managerRegistry;

/**
* @var EntityManager&MockObject
* @var ObjectManager&MockObject
*/
private EntityManager $entityManager;
private ObjectManager $objectManager;

/**
* @var ClassMetadata&MockObject
Expand All @@ -45,34 +48,50 @@ protected function setUp(): void
'bar' => 'BarEntity',
];

$this->entityManager = $this->createMock(EntityManager::class);
$this->entityManager->method('getClassMetadata')->with('MyEntityClass')
$this->objectManager = $this->createMock(ObjectManager::class);
$this->objectManager->method('getClassMetadata')->with('MyEntityClass')
->willReturn($this->classMetadata)
;

$this->baseEntityManager = $this->createMock(BaseEntityManager::class);
$this->baseEntityManager->method('getClass')
->willReturn('MyEntityClass')
;
$this->baseEntityManager->method('getEntityManager')
->willReturn($this->entityManager)
$this->managerRegistry = $this->createMock(ManagerRegistry::class);
$this->managerRegistry->method('getManagerForClass')->with('MyEntityClass')
->willReturn($this->objectManager)
;

parent::setUp();
}

public function testTypesAreSelectable(): void
{
$choices = $this->factory->create($this->getTestedType())
$choices = $this->factory->create($this->getTestedType(), null, [
'class' => 'MyEntityClass',
])
->createView()->vars['choices'];

static::assertContainsEquals(new ChoiceView('foo', 'foo', 'foo'), $choices);
static::assertContainsEquals(new ChoiceView('bar', 'bar', 'bar'), $choices);
}

public function testPassIdAndNameToViewWithGrandParent(): void
{
$builder = $this->factory->createNamedBuilder('parent', FormType::class)
->add('child', FormType::class)
;
$builder->get('child')->add('grand_child', $this->getTestedType(), [
'class' => 'MyEntityClass',
]);
$view = $builder->getForm()->createView();

static::assertSame('parent_child_grand_child', $view['child']['grand_child']->vars['id']);
static::assertSame('grand_child', $view['child']['grand_child']->vars['name']);
static::assertSame('parent[child][grand_child]', $view['child']['grand_child']->vars['full_name']);
}

public function testUnknownTypeIsNotIncluded(): void
{
$choices = $this->factory->create($this->getTestedType(), 'types')
$choices = $this->factory->create($this->getTestedType(), 'types', [
'class' => 'MyEntityClass',
])
->createView()->vars['choices'];

$countryCodes = [];
Expand All @@ -91,12 +110,52 @@ public function testSubmitNull($expected = null, $norm = null, $view = ''): void

public function testSubmitNullUsesDefaultEmptyData($emptyData = 'foo', $expectedData = 'foo'): void
{
parent::testSubmitNullUsesDefaultEmptyData($emptyData, $expectedData);
$builder = $this->factory->createBuilder($this->getTestedType(), null, [
'class' => 'MyEntityClass',
]);

if ($builder->getCompound()) {
$emptyData = [];
foreach ($builder as $field) {
// empty children should map null (model data) in the compound view data
$emptyData[$field->getName()] = null;
}
} else {
// simple fields share the view and the model format, unless they use a transformer
$expectedData = $emptyData;
}

$form = $builder->setEmptyData($emptyData)->getForm()->submit(null);

static::assertSame($emptyData, $form->getViewData());
static::assertSame($expectedData, $form->getNormData());
static::assertSame($expectedData, $form->getData());
}

protected function create(mixed $data = null, array $options = []): FormInterface
{
return parent::create($data, array_merge([
'class' => 'MyEntityClass',
], $options));
}

protected function createNamed(string $name, mixed $data = null, array $options = []): FormInterface
{
return parent::createNamed($name, $data, array_merge([
'class' => 'MyEntityClass',
], $options));
}

protected function createBuilder(array $parentOptions = [], array $childOptions = []): FormBuilderInterface
{
return parent::createBuilder($parentOptions, array_merge([
'class' => 'MyEntityClass',
], $childOptions));
}

protected function getTestedType(): string
{
return EntityDoctrineDiscriminatorType::class;
return DoctrineDiscriminatorType::class;
}

/**
Expand All @@ -105,7 +164,7 @@ protected function getTestedType(): string
protected function getTypes(): array
{
return [
new EntityDoctrineDiscriminatorType($this->baseEntityManager),
new DoctrineDiscriminatorType($this->managerRegistry),
];
}
}

0 comments on commit 460c59b

Please sign in to comment.