diff --git a/src/Admin/AbstractAdmin.php b/src/Admin/AbstractAdmin.php index d24e31c47ba..da7ceda3067 100644 --- a/src/Admin/AbstractAdmin.php +++ b/src/Admin/AbstractAdmin.php @@ -2885,6 +2885,26 @@ final public function hasTemplateRegistry(): bool return null !== $this->templateRegistry; } + public function createFieldDescription(string $propertyName, array $options = []): FieldDescriptionInterface + { + $fieldDescriptionFactory = $this->getFieldDescriptionFactory(); + + // NEXT_MAJOR: Remove the "if" block and leave the "else" one. + if (null === $fieldDescriptionFactory) { + $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance( + $this->getClass(), + $propertyName, + $options + ); + } else { + $fieldDescription = $fieldDescriptionFactory->create($this->getClass(), $propertyName, $options); + } + + $fieldDescription->setAdmin($this); + + return $fieldDescription; + } + /** * @phpstan-param T $object */ @@ -3344,26 +3364,6 @@ private function buildRoutes(): void $extension->configureRoutes($this, $this->routes); } } - - private function createFieldDescription(string $name, array $options = []): FieldDescriptionInterface - { - $fieldDescriptionFactory = $this->getFieldDescriptionFactory(); - - // NEXT_MAJOR: Remove the "if" block and leave the "else" one. - if (null === $fieldDescriptionFactory) { - $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance( - $this->getClass(), - $name, - $options - ); - } else { - $fieldDescription = $fieldDescriptionFactory->create($this->getClass(), $name, $options); - } - - $fieldDescription->setAdmin($this); - - return $fieldDescription; - } } class_exists(\Sonata\Form\Validator\ErrorElement::class); diff --git a/src/Admin/AdminInterface.php b/src/Admin/AdminInterface.php index 8b5d82b7ee8..1798f74d15b 100644 --- a/src/Admin/AdminInterface.php +++ b/src/Admin/AdminInterface.php @@ -51,6 +51,7 @@ * @method string|null getParentAssociationMapping() * @method void reorderFormGroup(string $group, array $keys) * @method void defineFormBuilder(FormBuilderInterface $formBuilder) + * @method FieldDescriptionInterface createFieldDescription(string $propertyName, array $options = []) * * @phpstan-template T of object * @phpstan-extends AccessRegistryInterface @@ -789,6 +790,9 @@ public function getListMode(); // * the getFormBuilder is only call by the main admin class. // */ // public function defineFormBuilder(FormBuilderInterface $formBuilder): void; + +// NEXT_MAJOR: uncomment this method in 4.0 +// public function createFieldDescription(string $propertyName, array $options = []): FieldDescriptionInterface; } class_exists(\Sonata\Form\Validator\ErrorElement::class); diff --git a/src/Datagrid/DatagridMapper.php b/src/Datagrid/DatagridMapper.php index e7b2e5faa82..f7e35da2355 100644 --- a/src/Datagrid/DatagridMapper.php +++ b/src/Datagrid/DatagridMapper.php @@ -114,11 +114,19 @@ public function add( $filterOptions['field_name'] = substr(strrchr('.'.$name, '.'), 1); } - $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance( - $this->admin->getClass(), - $name, - array_merge($filterOptions, $fieldDescriptionOptions) - ); + // NEXT_MAJOR: Remove the check and use `createFieldDescription`. + if (method_exists($this->admin, 'createFieldDescription')) { + $fieldDescription = $this->admin->createFieldDescription( + $name, + array_merge($filterOptions, $fieldDescriptionOptions) + ); + } else { + $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance( + $this->admin->getClass(), + $name, + array_merge($filterOptions, $fieldDescriptionOptions) + ); + } } else { throw new \TypeError( 'Unknown field name in datagrid mapper.' diff --git a/src/Datagrid/ListMapper.php b/src/Datagrid/ListMapper.php index f810cf97bcc..fdd30c410e4 100644 --- a/src/Datagrid/ListMapper.php +++ b/src/Datagrid/ListMapper.php @@ -144,11 +144,19 @@ public function add($name, $type = null, array $fieldDescriptionOptions = []) )); } - $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance( - $this->admin->getClass(), - $name, - $fieldDescriptionOptions - ); + // NEXT_MAJOR: Remove the check and use `createFieldDescription`. + if (method_exists($this->admin, 'createFieldDescription')) { + $fieldDescription = $this->admin->createFieldDescription( + $name, + $fieldDescriptionOptions + ); + } else { + $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance( + $this->admin->getClass(), + $name, + $fieldDescriptionOptions + ); + } } else { throw new \TypeError( 'Unknown field name in list mapper.' diff --git a/src/Form/FormMapper.php b/src/Form/FormMapper.php index 1f0b98ed8a1..00cfffee103 100644 --- a/src/Form/FormMapper.php +++ b/src/Form/FormMapper.php @@ -108,11 +108,19 @@ public function add($name, $type = null, array $options = [], array $fieldDescri $fieldDescriptionOptions['translation_domain'] = $group['translation_domain']; } - $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance( - $this->admin->getClass(), - $name instanceof FormBuilderInterface ? $name->getName() : $name, - $fieldDescriptionOptions - ); + // NEXT_MAJOR: Remove the check and use `createFieldDescription`. + if (method_exists($this->admin, 'createFieldDescription')) { + $fieldDescription = $this->admin->createFieldDescription( + $name instanceof FormBuilderInterface ? $name->getName() : $name, + $fieldDescriptionOptions + ); + } else { + $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance( + $this->admin->getClass(), + $name instanceof FormBuilderInterface ? $name->getName() : $name, + $fieldDescriptionOptions + ); + } // Note that the builder var is actually the formContractor: $this->builder->fixFieldDescription($this->admin, $fieldDescription); diff --git a/src/Show/ShowMapper.php b/src/Show/ShowMapper.php index cf5cb54120b..9809eaa7fbc 100644 --- a/src/Show/ShowMapper.php +++ b/src/Show/ShowMapper.php @@ -71,11 +71,20 @@ public function add($name, $type = null, array $fieldDescriptionOptions = []) $fieldDescription->mergeOptions($fieldDescriptionOptions); } elseif (\is_string($name)) { if (!$this->admin->hasShowFieldDescription($name)) { - $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance( - $this->admin->getClass(), - $name, - $fieldDescriptionOptions - ); + + // NEXT_MAJOR: Remove the check and use `createFieldDescription`. + if (method_exists($this->admin, 'createFieldDescription')) { + $fieldDescription = $this->admin->createFieldDescription( + $name, + $fieldDescriptionOptions + ); + } else { + $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance( + $this->admin->getClass(), + $name, + $fieldDescriptionOptions + ); + } } else { throw new \LogicException(sprintf( 'Duplicate field name "%s" in show mapper. Names should be unique.', diff --git a/tests/Admin/AdminTest.php b/tests/Admin/AdminTest.php index a878f4c5ba9..72fc90e7a31 100644 --- a/tests/Admin/AdminTest.php +++ b/tests/Admin/AdminTest.php @@ -34,6 +34,7 @@ use Sonata\AdminBundle\Datagrid\PagerInterface; use Sonata\AdminBundle\Datagrid\ProxyQueryInterface; use Sonata\AdminBundle\Exporter\DataSourceInterface; +use Sonata\AdminBundle\FieldDescription\FieldDescriptionFactoryInterface; use Sonata\AdminBundle\Filter\Persister\FilterPersisterInterface; use Sonata\AdminBundle\Model\AuditManagerInterface; use Sonata\AdminBundle\Model\ModelManagerInterface; @@ -1693,11 +1694,11 @@ public function testFormAddPostSubmitEventForPreValidation(): void ->willReturn($this->createMock(MemberMetadata::class)); $modelAdmin->setValidator($validator); - $modelManager = $this->createMock(ModelManagerInterface::class); - $modelManager - ->method('getNewFieldDescriptionInstance') + $fieldDescriptionFactory = $this->createStub(FieldDescriptionFactoryInterface::class); + $fieldDescriptionFactory + ->method('create') ->willReturn(new FieldDescription('name')); - $modelAdmin->setModelManager($modelManager); + $modelAdmin->setFieldDescriptionFactory($fieldDescriptionFactory); // a Admin class to test that preValidate is called $testAdminPreValidate = $this->createMock(AbstractAdmin::class); @@ -1763,11 +1764,11 @@ public function testCanAddInlineValidationOnlyForGenericMetadata(): void ->willReturn($metadata); $modelAdmin->setValidator($validator); - $modelManager = $this->createStub(ModelManagerInterface::class); - $modelManager - ->method('getNewFieldDescriptionInstance') + $fieldDescriptionFactory = $this->createStub(FieldDescriptionFactoryInterface::class); + $fieldDescriptionFactory + ->method('create') ->willReturn(new FieldDescription('name')); - $modelAdmin->setModelManager($modelManager); + $modelAdmin->setFieldDescriptionFactory($fieldDescriptionFactory); $event = $this->createStub(FormEvent::class); $event @@ -1902,13 +1903,10 @@ public function testGetFilterFieldDescription(): void $barFieldDescription = new FieldDescription('bar'); $bazFieldDescription = new FieldDescription('baz'); - $modelManager = $this->createMock(ModelManagerInterface::class); - $modelManager - ->method('getDefaultSortValues') - ->willReturn([]); - - $modelManager->expects($this->exactly(3)) - ->method('getNewFieldDescriptionInstance') + $fieldDescriptionFactory = $this->createMock(FieldDescriptionFactoryInterface::class); + $fieldDescriptionFactory + ->expects($this->exactly(3)) + ->method('create') ->willReturnCallback(static function ($adminClass, string $name, $filterOptions) use ($fooFieldDescription, $barFieldDescription, $bazFieldDescription) { switch ($name) { case 'foo': @@ -1936,6 +1934,13 @@ public function testGetFilterFieldDescription(): void return $fieldDescription; }); + $modelAdmin->setFieldDescriptionFactory($fieldDescriptionFactory); + + $modelManager = $this->createStub(ModelManagerInterface::class); + $modelManager + ->method('getDefaultSortValues') + ->willReturn([]); + $modelAdmin->setModelManager($modelManager); $pager = $this->createMock(PagerInterface::class); diff --git a/tests/Datagrid/DatagridMapperTest.php b/tests/Datagrid/DatagridMapperTest.php index f932eb53cdd..c2f40ee8a6a 100644 --- a/tests/Datagrid/DatagridMapperTest.php +++ b/tests/Datagrid/DatagridMapperTest.php @@ -25,7 +25,6 @@ use Sonata\AdminBundle\Datagrid\ProxyQueryInterface; use Sonata\AdminBundle\Filter\Filter; use Sonata\AdminBundle\Filter\FilterInterface; -use Sonata\AdminBundle\Model\ModelManagerInterface; use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilder; @@ -60,7 +59,10 @@ protected function setUp(): void $this->datagrid = new Datagrid($proxyQuery, $fieldDescriptionCollection, $pager, $formBuilder, []); - $admin = $this->createMock(AdminInterface::class); + // NEXT_MAJOR: Change to $this->createStub(AdminInterface::class). + $admin = $this->getMockBuilder(AdminInterface::class) + ->addMethods(['createFieldDescription']) + ->getMockForAbstractClass(); $datagridBuilder ->method('addFilter') @@ -82,21 +84,15 @@ protected function setUp(): void $datagrid->addFilter($filter); }); - $modelManager = $this->createMock(ModelManagerInterface::class); - - $modelManager - ->method('getNewFieldDescriptionInstance') - ->willReturnCallback(function (?string $class, string $name, array $options = []): BaseFieldDescription { + $admin + ->method('createFieldDescription') + ->willReturnCallback(function (string $name, array $options = []): FieldDescriptionInterface { $fieldDescription = $this->getFieldDescriptionMock($name); $fieldDescription->setOptions($options); return $fieldDescription; }); - $admin - ->method('getModelManager') - ->willReturn($modelManager); - $admin ->method('isGranted') ->willReturnCallback(static function (string $name, ?object $object = null): bool { diff --git a/tests/Datagrid/ListMapperTest.php b/tests/Datagrid/ListMapperTest.php index 7b787e5e4aa..dfcc8b331ef 100644 --- a/tests/Datagrid/ListMapperTest.php +++ b/tests/Datagrid/ListMapperTest.php @@ -21,7 +21,6 @@ use Sonata\AdminBundle\Admin\FieldDescriptionInterface; use Sonata\AdminBundle\Builder\ListBuilderInterface; use Sonata\AdminBundle\Datagrid\ListMapper; -use Sonata\AdminBundle\Model\ModelManagerInterface; use Sonata\AdminBundle\Translator\NoopLabelTranslatorStrategy; /** @@ -64,21 +63,15 @@ protected function setUp(): void $list->add($fieldDescription); }); - $modelManager = $this->createMock(ModelManagerInterface::class); - - $modelManager - ->method('getNewFieldDescriptionInstance') - ->willReturnCallback(function (?string $class, string $name, array $options = []): BaseFieldDescription { + $this->admin + ->method('createFieldDescription') + ->willReturnCallback(function (string $name, array $options = []): FieldDescriptionInterface { $fieldDescription = $this->getFieldDescriptionMock($name); $fieldDescription->setOptions($options); return $fieldDescription; }); - $this->admin - ->method('getModelManager') - ->willReturn($modelManager); - $labelTranslatorStrategy = new NoopLabelTranslatorStrategy(); $this->admin diff --git a/tests/Form/FormMapperTest.php b/tests/Form/FormMapperTest.php index d4993f05471..13e3d2829d5 100644 --- a/tests/Form/FormMapperTest.php +++ b/tests/Form/FormMapperTest.php @@ -16,7 +16,9 @@ use PHPUnit\Framework\TestCase; use Sonata\AdminBundle\Admin\AdminInterface; use Sonata\AdminBundle\Admin\BaseFieldDescription; +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; use Sonata\AdminBundle\Builder\FormContractorInterface; +use Sonata\AdminBundle\FieldDescription\FieldDescriptionFactoryInterface; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Model\ModelManagerInterface; use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface; @@ -91,18 +93,17 @@ protected function setUp(): void $this->admin->setSecurityHandler($securityHandler); $this->admin->setFormContractor($this->contractor); - $this->modelManager = $this->getMockForAbstractClass(ModelManagerInterface::class); - - $this->modelManager - ->method('getNewFieldDescriptionInstance') - ->willReturnCallback(function (string $class, string $name, array $options = []): BaseFieldDescription { + $fieldDescriptionFactory = $this->createStub(FieldDescriptionFactoryInterface::class); + $fieldDescriptionFactory + ->method('create') + ->willReturnCallback(function (string $class, string $name, array $options = []): FieldDescriptionInterface { $fieldDescription = $this->getFieldDescriptionMock($name); $fieldDescription->setOptions($options); return $fieldDescription; }); - $this->admin->setModelManager($this->modelManager); + $this->admin->setFieldDescriptionFactory($fieldDescriptionFactory); $labelTranslatorStrategy = $this->getMockForAbstractClass(LabelTranslatorStrategyInterface::class); $this->admin->setLabelTranslatorStrategy($labelTranslatorStrategy); diff --git a/tests/Show/ShowMapperTest.php b/tests/Show/ShowMapperTest.php index 3b75e5fa440..b41cfebcc33 100644 --- a/tests/Show/ShowMapperTest.php +++ b/tests/Show/ShowMapperTest.php @@ -19,7 +19,7 @@ use Sonata\AdminBundle\Admin\FieldDescriptionCollection; use Sonata\AdminBundle\Admin\FieldDescriptionInterface; use Sonata\AdminBundle\Builder\ShowBuilderInterface; -use Sonata\AdminBundle\Model\ModelManagerInterface; +use Sonata\AdminBundle\FieldDescription\FieldDescriptionFactoryInterface; use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface; use Sonata\AdminBundle\Show\ShowMapper; use Sonata\AdminBundle\Tests\App\Builder\ShowBuilder; @@ -69,7 +69,9 @@ protected function setUp(): void { $this->showBuilder = $this->getMockForAbstractClass(ShowBuilderInterface::class); $this->fieldDescriptionCollection = new FieldDescriptionCollection(); - $this->admin = $this->getMockForAbstractClass(AdminInterface::class); + $this->admin = $this->getMockBuilder(AdminInterface::class) + ->addMethods(['createFieldDescription']) + ->getMockForAbstractClass(); $this->admin ->method('getLabel') @@ -100,21 +102,15 @@ protected function setUp(): void $this->groups[$group]['fields'] = array_merge(array_flip($keys), $this->groups[$group]['fields']); }); - $modelManager = $this->getMockForAbstractClass(ModelManagerInterface::class); - - $modelManager - ->method('getNewFieldDescriptionInstance') - ->willReturnCallback(function (?string $class, string $name, array $options = []) { + $this->admin + ->method('createFieldDescription') + ->willReturnCallback(function (string $name, array $options = []): FieldDescriptionInterface { $fieldDescription = $this->getFieldDescriptionMock($name); $fieldDescription->setOptions($options); return $fieldDescription; }); - $this->admin - ->method('getModelManager') - ->willReturn($modelManager); - $labelTranslatorStrategy = new NoopLabelTranslatorStrategy(); $this->admin @@ -580,10 +576,9 @@ private function cleanShowMapper(): void $this->showMapper = new ShowMapper($this->showBuilder, $this->fieldDescriptionCollection, $this->admin); - $modelManager = $this->getMockForAbstractClass(ModelManagerInterface::class); - - $modelManager - ->method('getNewFieldDescriptionInstance') + $fieldDescriptionFactory = $this->createStub(FieldDescriptionFactoryInterface::class); + $fieldDescriptionFactory + ->method('create') ->willReturnCallback(function (string $class, string $name, array $options = []): FieldDescriptionInterface { $fieldDescription = $this->getFieldDescriptionMock($name); $fieldDescription->setOptions($options); @@ -591,7 +586,7 @@ private function cleanShowMapper(): void return $fieldDescription; }); - $this->admin->setModelManager($modelManager); + $this->admin->setFieldDescriptionFactory($fieldDescriptionFactory); $this->admin->setLabelTranslatorStrategy(new NoopLabelTranslatorStrategy()); $this->admin->setShowBuilder(new ShowBuilder());