From e60ebfeb595799103b20865bb0852f214a6cbb56 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Tue, 16 Feb 2021 01:53:57 +0100 Subject: [PATCH 1/6] Introduce FieldFactoryInterface This interface will be responsible of creating FieldDescriptionInterface instances. --- UPGRADE-3.x.md | 4 ++ src/Admin/AbstractAdmin.php | 34 +++++++++---- .../Admin/AbstractTaggedAdmin.php | 32 ++++++++++++ .../Admin/TaggedAdminInterface.php | 51 ++++++++++++------- .../AddDependencyCallsCompilerPass.php | 7 +++ .../FieldDescriptionFactoryInterface.php | 24 +++++++++ src/Model/ModelManagerInterface.php | 2 + .../FieldDescriptionFactory.php | 34 +++++++++++++ tests/App/Model/ModelManager.php | 1 + tests/App/config/services.yml | 3 ++ 10 files changed, 163 insertions(+), 29 deletions(-) create mode 100644 src/FieldDescription/FieldDescriptionFactoryInterface.php create mode 100644 tests/App/FieldDescription/FieldDescriptionFactory.php diff --git a/UPGRADE-3.x.md b/UPGRADE-3.x.md index d5e0ed5b17..8048a88e35 100644 --- a/UPGRADE-3.x.md +++ b/UPGRADE-3.x.md @@ -11,6 +11,10 @@ Use `Sonata\AdminBundle\Admin\AbstractAdmin::createNewInstance()` method instead UPGRADE FROM 3.88 to 3.89 ========================= +### Deprecated `Sonata\AdminBundle\Model\ModelManager::getNewFieldDescriptionInstance()` method. + +This method has been deprecated in favor of `FieldFactoryInterface::create()`. + ### Deprecated overriding `AbstractAdmin::getNewInstance()`. Use `AbstractAdmin::alterNewInstance()` instead. diff --git a/src/Admin/AbstractAdmin.php b/src/Admin/AbstractAdmin.php index 03873bb246..3da19002eb 100644 --- a/src/Admin/AbstractAdmin.php +++ b/src/Admin/AbstractAdmin.php @@ -746,10 +746,8 @@ public function buildDatagrid() if ($this->hasListFieldDescription($filterParameters['_sort_by'])) { $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']); } else { - $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance( - $this->getClass(), - $filterParameters['_sort_by'], - [] + $filterParameters['_sort_by'] = $this->createFieldDescription( + $filterParameters['_sort_by'] ); $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this); @@ -3096,8 +3094,7 @@ protected function buildList() $mapper = new ListMapper($this->getListBuilder(), $this->list, $this); if (\count($this->getBatchActions()) > 0 && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) { - $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance( - $this->getClass(), + $fieldDescription = $this->createFieldDescription( 'batch', [ 'label' => 'batch', @@ -3107,7 +3104,6 @@ protected function buildList() ] ); - $fieldDescription->setAdmin($this); // NEXT_MAJOR: Remove this line and use commented line below it instead $fieldDescription->setTemplate($this->getTemplate('batch')); // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch')); @@ -3122,8 +3118,7 @@ protected function buildList() } if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) { - $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance( - $this->getClass(), + $fieldDescription = $this->createFieldDescription( 'select', [ 'label' => false, @@ -3133,7 +3128,6 @@ protected function buildList() ] ); - $fieldDescription->setAdmin($this); // NEXT_MAJOR: Remove this line and use commented line below it instead $fieldDescription->setTemplate($this->getTemplate('select')); // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select')); @@ -3370,6 +3364,26 @@ 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/DependencyInjection/Admin/AbstractTaggedAdmin.php b/src/DependencyInjection/Admin/AbstractTaggedAdmin.php index c28da8d463..ef98b16098 100644 --- a/src/DependencyInjection/Admin/AbstractTaggedAdmin.php +++ b/src/DependencyInjection/Admin/AbstractTaggedAdmin.php @@ -22,6 +22,7 @@ use Sonata\AdminBundle\Builder\ShowBuilderInterface; use Sonata\AdminBundle\Datagrid\Pager; use Sonata\AdminBundle\Exporter\DataSourceInterface; +use Sonata\AdminBundle\FieldDescription\FieldDescriptionFactoryInterface; use Sonata\AdminBundle\Filter\Persister\FilterPersisterInterface; use Sonata\AdminBundle\Model\ModelManagerInterface; use Sonata\AdminBundle\Route\RouteGeneratorInterface; @@ -202,6 +203,11 @@ abstract class AbstractTaggedAdmin implements TaggedAdminInterface */ protected $labelTranslatorStrategy; + /** + * @var FieldDescriptionFactoryInterface|null + */ + private $fieldDescriptionFactory; + /** * NEXT_MAJOR: Change signature to __construct(string $code, string $class, string $baseControllerName). * @@ -423,6 +429,32 @@ public function getDataSource(): ?DataSourceInterface return $this->dataSource; } + final public function setFieldDescriptionFactory(FieldDescriptionFactoryInterface $fieldDescriptionFactory): void + { + $this->fieldDescriptionFactory = $fieldDescriptionFactory; + } + + /** + * NEXT_MAJOR: Change typehint for FieldDescriptionFactoryInterface. + */ + public function getFieldDescriptionFactory(): ?FieldDescriptionFactoryInterface + { + if (null === $this->fieldDescriptionFactory) { + // NEXT_MAJOR: Remove this deprecation and uncomment the following exception + @trigger_error(sprintf( + 'Calling %s() when no field description factory is set is deprecated since sonata-project/admin-bundle 3.x' + .' and will throw a LogicException in 4.0', + __METHOD__, + ), \E_USER_DEPRECATED); +// throw new \LogicException(sprintf( +// 'Admin "%s" has no field description factory.', +// static::class +// )); + } + + return $this->fieldDescriptionFactory; + } + /** * @final since sonata-admin/admin-bundle 3.84 */ diff --git a/src/DependencyInjection/Admin/TaggedAdminInterface.php b/src/DependencyInjection/Admin/TaggedAdminInterface.php index 49b80e0e1f..4043865a36 100644 --- a/src/DependencyInjection/Admin/TaggedAdminInterface.php +++ b/src/DependencyInjection/Admin/TaggedAdminInterface.php @@ -21,6 +21,7 @@ use Sonata\AdminBundle\Builder\RouteBuilderInterface; use Sonata\AdminBundle\Builder\ShowBuilderInterface; use Sonata\AdminBundle\Exporter\DataSourceInterface; +use Sonata\AdminBundle\FieldDescription\FieldDescriptionFactoryInterface; use Sonata\AdminBundle\Filter\Persister\FilterPersisterInterface; use Sonata\AdminBundle\Model\ModelManagerInterface; use Sonata\AdminBundle\Route\RouteGeneratorInterface; @@ -41,25 +42,27 @@ * - The first and third argument are automatically injected by the AddDependencyCallsCompilerPass. * - The second one is used as a reference of the Admin in the Pool, with the `setAdminClasses` call. * - * @method void initialize() - * @method void setLabel(?string $label) - * @method void showMosaicButton(bool $isShown) - * @method void setPagerType(string $pagerType) - * @method string getPagerType() - * @method void setManagerType(string $managerType) - * @method void setSecurityInformation(array $information) - * @method void setFilterPersister(?FilterPersisterInterface $filterPersister = null) - * @method FilterPersisterInterface|null getFilterPersister() - * @method bool hasFilterPersister() - * @method void setModelManager(ModelManagerInterface $modelManager) - * @method void setDataSource(DataSourceInterface $dataSource) - * @method DataSourceInterface getDataSource() - * @method FormContractorInterface getFormContractor() - * @method void setShowBuilder(ShowBuilderInterface $showBuilder) - * @method ShowBuilderInterface getShowBuilder() - * @method Pool getConfigurationPool() - * @method void setRouteGenerator(RouteGeneratorInterface $routeGenerator) - * @method RouteGeneratorInterface getRouteGenerator() + * @method void initialize() + * @method void setLabel(?string $label) + * @method void showMosaicButton(bool $isShown) + * @method void setPagerType(string $pagerType) + * @method string getPagerType() + * @method void setManagerType(string $managerType) + * @method void setSecurityInformation(array $information) + * @method void setFilterPersister(?FilterPersisterInterface $filterPersister = null) + * @method FilterPersisterInterface|null getFilterPersister() + * @method bool hasFilterPersister() + * @method void setModelManager(ModelManagerInterface $modelManager) + * @method void setDataSource(DataSourceInterface $dataSource) + * @method DataSourceInterface getDataSource() + * @method void setFieldDescriptionFactory(FieldDescriptionFactoryInterface $fieldDescriptionFactory) + * @method FieldDescriptionFactoryInterface getFieldDescriptionFactory() + * @method FormContractorInterface getFormContractor() + * @method void setShowBuilder(ShowBuilderInterface $showBuilder) + * @method ShowBuilderInterface getShowBuilder() + * @method Pool getConfigurationPool() + * @method void setRouteGenerator(RouteGeneratorInterface $routeGenerator) + * @method RouteGeneratorInterface getRouteGenerator() */ interface TaggedAdminInterface { @@ -168,6 +171,16 @@ public function getModelManager(); */ // public function getDataSource(): DataSourceInterface; + /** + * NEXT_MAJOR: Uncomment this method. + */ +// public function setFieldDescriptionFactory(FieldDescriptionFactoryInterface $fieldDescriptionFactory): void; + + /** + * NEXT_MAJOR: Uncomment this method. + */ +// public function getFieldDescriptionFactory(): FieldDescriptionFactoryInterface; + /** * @return void */ diff --git a/src/DependencyInjection/Compiler/AddDependencyCallsCompilerPass.php b/src/DependencyInjection/Compiler/AddDependencyCallsCompilerPass.php index 9e13110c35..d42ba2945b 100644 --- a/src/DependencyInjection/Compiler/AddDependencyCallsCompilerPass.php +++ b/src/DependencyInjection/Compiler/AddDependencyCallsCompilerPass.php @@ -253,6 +253,7 @@ public function applyConfigurationFromAttribute(Definition $definition, array $a $keys = [ 'model_manager', 'data_source', + 'field_description_factory', 'form_contractor', 'show_builder', 'list_builder', @@ -301,6 +302,7 @@ public function applyDefaults(ContainerBuilder $container, $serviceId, array $at $defaultAddServices = [ 'model_manager' => sprintf('sonata.admin.manager.%s', $managerType), 'data_source' => sprintf('sonata.admin.data_source.%s', $managerType), + 'field_description_factory' => sprintf('sonata.admin.field_description_factory.%s', $managerType), 'form_contractor' => sprintf('sonata.admin.builder.%s_form', $managerType), 'show_builder' => sprintf('sonata.admin.builder.%s_show', $managerType), 'list_builder' => sprintf('sonata.admin.builder.%s_list', $managerType), @@ -324,6 +326,11 @@ public function applyDefaults(ContainerBuilder $container, $serviceId, array $at continue; } + // NEXT_MAJOR: Remove this check + if ('field_description_factory' === $attr && !$container->has($addServiceId)) { + continue; + } + $method = $this->generateSetterMethodName($attr); if (isset($overwriteAdminConfiguration[$attr]) || !$definition->hasMethodCall($method)) { diff --git a/src/FieldDescription/FieldDescriptionFactoryInterface.php b/src/FieldDescription/FieldDescriptionFactoryInterface.php new file mode 100644 index 0000000000..e76f8292d0 --- /dev/null +++ b/src/FieldDescription/FieldDescriptionFactoryInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\FieldDescription; + +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; + +interface FieldDescriptionFactoryInterface +{ + /** + * @phpstan-param class-string $class + */ + public function create(string $class, string $name, array $options = []): FieldDescriptionInterface; +} diff --git a/src/Model/ModelManagerInterface.php b/src/Model/ModelManagerInterface.php index 355e849a3e..158cb7ffea 100644 --- a/src/Model/ModelManagerInterface.php +++ b/src/Model/ModelManagerInterface.php @@ -29,6 +29,8 @@ interface ModelManagerInterface extends DatagridManagerInterface { /** + * @deprecated since sonata-project/admin-bundle 3.x. + * * @param string $class * @param string $name * diff --git a/tests/App/FieldDescription/FieldDescriptionFactory.php b/tests/App/FieldDescription/FieldDescriptionFactory.php new file mode 100644 index 0000000000..7b40781d23 --- /dev/null +++ b/tests/App/FieldDescription/FieldDescriptionFactory.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\Tests\App\FieldDescription; + +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; +use Sonata\AdminBundle\FieldDescription\FieldDescriptionFactoryInterface; +use Sonata\AdminBundle\Tests\App\Admin\FieldDescription; + +final class FieldDescriptionFactory implements FieldDescriptionFactoryInterface +{ + public function create(string $class, string $name, array $options = []): FieldDescriptionInterface + { + if (!isset($options['route']['name'])) { + $options['route']['name'] = 'edit'; + } + + if (!isset($options['route']['parameters'])) { + $options['route']['parameters'] = []; + } + + return new FieldDescription($name, $options); + } +} diff --git a/tests/App/Model/ModelManager.php b/tests/App/Model/ModelManager.php index 1014eaedb1..29ab4ff8de 100644 --- a/tests/App/Model/ModelManager.php +++ b/tests/App/Model/ModelManager.php @@ -31,6 +31,7 @@ public function __construct(FooRepository $repository) $this->repository = $repository; } + // NEXT_MAJOR: Remove this method. public function getNewFieldDescriptionInstance($class, $name, array $options = []) { if (!isset($options['route']['name'])) { diff --git a/tests/App/config/services.yml b/tests/App/config/services.yml index 7b79e5f83c..09a4e179af 100644 --- a/tests/App/config/services.yml +++ b/tests/App/config/services.yml @@ -37,6 +37,9 @@ services: sonata.admin.data_source.test: class: Sonata\AdminBundle\Tests\App\Exporter\DataSource + sonata.admin.field_description_factory.test: + class: Sonata\AdminBundle\Tests\App\FieldDescription\FieldDescriptionFactory + Sonata\AdminBundle\Tests\App\Admin\FooAdmin: arguments: [~, Sonata\AdminBundle\Tests\App\Model\Foo, Sonata\AdminBundle\Controller\CRUDController] tags: From 218e433072caf9d6b84ea021e457e10747033e9d Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Tue, 16 Feb 2021 01:55:07 +0100 Subject: [PATCH 2/6] Introduce TypeGuesserInterface This will guess the field description type from its properties. --- src/FieldDescription/TypeGuesserInterface.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/FieldDescription/TypeGuesserInterface.php diff --git a/src/FieldDescription/TypeGuesserInterface.php b/src/FieldDescription/TypeGuesserInterface.php new file mode 100644 index 0000000000..998e25a49a --- /dev/null +++ b/src/FieldDescription/TypeGuesserInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\FieldDescription; + +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; +use Symfony\Component\Form\Guess\TypeGuess; + +interface TypeGuesserInterface +{ + public function guess(FieldDescriptionInterface $fieldDescription): TypeGuess; +} From 5274f8f520e48a294db7f810c21f4a173e80e943 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Sun, 21 Feb 2021 01:45:25 +0100 Subject: [PATCH 3/6] Add AdminInterface::createFieldDescription method This method will use the field description factory and also sets the current admin to the field description. --- src/Admin/AbstractAdmin.php | 40 +++++++++++++-------------- src/Admin/AdminInterface.php | 4 +++ src/Datagrid/DatagridMapper.php | 18 ++++++++---- src/Datagrid/ListMapper.php | 18 ++++++++---- src/Form/FormMapper.php | 18 ++++++++---- src/Show/ShowMapper.php | 19 +++++++++---- tests/Admin/AdminTest.php | 35 +++++++++++++---------- tests/Datagrid/DatagridMapperTest.php | 18 +++++------- tests/Datagrid/ListMapperTest.php | 23 ++++++--------- tests/Form/FormMapperTest.php | 13 +++++---- tests/Show/ShowMapperTest.php | 27 ++++++++---------- 11 files changed, 131 insertions(+), 102 deletions(-) diff --git a/src/Admin/AbstractAdmin.php b/src/Admin/AbstractAdmin.php index 3da19002eb..212fd906c5 100644 --- a/src/Admin/AbstractAdmin.php +++ b/src/Admin/AbstractAdmin.php @@ -2890,6 +2890,26 @@ final public function hasTemplateRegistry(): bool return null !== $this->templateRegistry; } + final 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-return T */ @@ -3364,26 +3384,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 8b5d82b7ee..1798f74d15 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 e7b2e5faa8..f7e35da235 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 f810cf97bc..fdd30c410e 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 1f0b98ed8a..00cfffee10 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 cf5cb54120..9809eaa7fb 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 fb9e32360d..7a8721c50f 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 f932eb53cd..c2f40ee8a6 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 7b787e5e4a..7a0db9608e 100644 --- a/tests/Datagrid/ListMapperTest.php +++ b/tests/Datagrid/ListMapperTest.php @@ -14,14 +14,12 @@ namespace Sonata\AdminBundle\Tests\Datagrid; use PHPUnit\Framework\TestCase; -use Sonata\AdminBundle\Admin\AbstractAdmin; use Sonata\AdminBundle\Admin\AdminInterface; use Sonata\AdminBundle\Admin\BaseFieldDescription; use Sonata\AdminBundle\Admin\FieldDescriptionCollection; 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; /** @@ -50,35 +48,32 @@ protected function setUp(): void { $listBuilder = $this->createMock(ListBuilderInterface::class); $this->fieldDescriptionCollection = new FieldDescriptionCollection(); - $this->admin = $this->createMock(AbstractAdmin::class); + + // NEXT_MAJOR: Change to $this->createStub(AdminInterface::class). + $this->admin = $this->getMockBuilder(AdminInterface::class) + ->addMethods(['createFieldDescription', 'hasAccess']) + ->getMockForAbstractClass(); $listBuilder ->method('addField') ->willReturnCallback(static function ( FieldDescriptionCollection $list, ?string $type, - BaseFieldDescription $fieldDescription, - AbstractAdmin $admin + BaseFieldDescription $fieldDescription ): void { $fieldDescription->setType($type); $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 d4993f0547..13e3d2829d 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 3b75e5fa44..b41cfebcc3 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()); From b0af0a7fd29b3d451bf9c4ddcb499219ef57e47f Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Sun, 28 Feb 2021 21:42:33 +0100 Subject: [PATCH 4/6] Add TypeGuesserChain for FieldDescription --- src/FieldDescription/TypeGuesserChain.php | 65 ++++++++++++++++++ .../FieldDescription/TypeGuesserChainTest.php | 66 +++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 src/FieldDescription/TypeGuesserChain.php create mode 100644 tests/FieldDescription/TypeGuesserChainTest.php diff --git a/src/FieldDescription/TypeGuesserChain.php b/src/FieldDescription/TypeGuesserChain.php new file mode 100644 index 0000000000..5297ceec1e --- /dev/null +++ b/src/FieldDescription/TypeGuesserChain.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\FieldDescription; + +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; +use Symfony\Component\Form\Guess\TypeGuess; + +/** + * The code is based on Symfony2 Form Components. + */ +final class TypeGuesserChain implements TypeGuesserInterface +{ + /** + * @var TypeGuesserInterface[] + */ + private $guessers = []; + + /** + * @param TypeGuesserInterface[] $guessers + */ + public function __construct(array $guessers) + { + $allGuessers = []; + + foreach ($guessers as $guesser) { + if (!$guesser instanceof TypeGuesserInterface) { + throw new \InvalidArgumentException(sprintf( + 'Expected argument of type "%s", "%s" given', + TypeGuesserInterface::class, + \is_object($guesser) ? \get_class($guesser) : \gettype($guesser) + )); + } + + if ($guesser instanceof self) { + $allGuessers[] = $guesser->guessers; + } else { + $allGuessers[] = [$guesser]; + } + } + + $this->guessers = array_merge(...$allGuessers); + } + + public function guess(FieldDescriptionInterface $fieldDescription): TypeGuess + { + $guesses = []; + + foreach ($this->guessers as $guesser) { + $guesses[] = $guesser->guess($fieldDescription); + } + + return TypeGuess::getBestGuess($guesses); + } +} diff --git a/tests/FieldDescription/TypeGuesserChainTest.php b/tests/FieldDescription/TypeGuesserChainTest.php new file mode 100644 index 0000000000..ef27c85ce0 --- /dev/null +++ b/tests/FieldDescription/TypeGuesserChainTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\Tests\FieldDescription; + +use PHPUnit\Framework\TestCase; +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; +use Sonata\AdminBundle\FieldDescription\TypeGuesserChain; +use Sonata\AdminBundle\FieldDescription\TypeGuesserInterface; +use Symfony\Component\Form\Guess\Guess; +use Symfony\Component\Form\Guess\TypeGuess; + +final class TypeGuesserChainTest extends TestCase +{ + public function testConstructorWithException(): void + { + $this->expectException(\InvalidArgumentException::class); + + new TypeGuesserChain([new \stdClass()]); + } + + public function testGuessType(): void + { + $typeGuess1 = new TypeGuess('foo1', [], Guess::MEDIUM_CONFIDENCE); + $guesser1 = $this->createStub(TypeGuesserInterface::class); + $guesser1 + ->method('guess') + ->willReturn($typeGuess1); + + $typeGuess2 = new TypeGuess('foo2', [], Guess::HIGH_CONFIDENCE); + $guesser2 = $this->createStub(TypeGuesserInterface::class); + $guesser2 + ->method('guess') + ->willReturn($typeGuess2); + + $typeGuess3 = new TypeGuess('foo3', [], Guess::LOW_CONFIDENCE); + $guesser3 = $this->createStub(TypeGuesserInterface::class); + $guesser3 + ->method('guess') + ->willReturn($typeGuess3); + + $fieldDescription = $this->createStub(FieldDescriptionInterface::class); + + $typeGuesserChain = new TypeGuesserChain([$guesser1, $guesser2, $guesser3]); + $this->assertSame($typeGuess2, $typeGuesserChain->guess($fieldDescription)); + + $typeGuess4 = new TypeGuess('foo4', [], Guess::LOW_CONFIDENCE); + $guesser4 = $this->createStub(TypeGuesserInterface::class); + $guesser4 + ->method('guess') + ->willReturn($typeGuess4); + + $typeGuesserChain = new TypeGuesserChain([$guesser4, $typeGuesserChain]); + $this->assertSame($typeGuess2, $typeGuesserChain->guess($fieldDescription)); + } +} From cb38c978b7f163d021a533d895f42281089ba081 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Sun, 28 Feb 2021 22:44:27 +0100 Subject: [PATCH 5/6] Deprecate TypeGuesserInterface from Guesser Deprecated in favor of Sonata\AdminBundle\FieldDescription\TypeGuesserInterface. --- UPGRADE-3.x.md | 8 ++++++++ src/Guesser/TypeGuesserChain.php | 5 +++++ src/Guesser/TypeGuesserInterface.php | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/UPGRADE-3.x.md b/UPGRADE-3.x.md index 8048a88e35..cf76f2c13f 100644 --- a/UPGRADE-3.x.md +++ b/UPGRADE-3.x.md @@ -4,6 +4,14 @@ UPGRADE 3.x UPGRADE FROM 3.89 to 3.90 ========================= +### Deprecated `Sonata\AdminBundle\Guesser\TypeGuesserInterface` interface. + +Use `Sonata\AdminBundle\FieldDescription\TypeGuesserInterface` interface instead. + +### Deprecated `Sonata\AdminBundle\Guesser\TypeGuesserChain` class. + +Use `Sonata\AdminBundle\FieldDescription\TypeGuesserChain` class instead. + ### Deprecated `Sonata\AdminBundle\Model\ModelManagerInterface::getModelInstance()` method. Use `Sonata\AdminBundle\Admin\AbstractAdmin::createNewInstance()` method instead. diff --git a/src/Guesser/TypeGuesserChain.php b/src/Guesser/TypeGuesserChain.php index fa6de1f9c0..5f696c0686 100644 --- a/src/Guesser/TypeGuesserChain.php +++ b/src/Guesser/TypeGuesserChain.php @@ -20,6 +20,11 @@ /** * The code is based on Symfony2 Form Components. * + * NEXT_MAJOR: Remove this class. + * + * @deprecated since sonata-project/admin-bundle 3.x, to be removed in 4.0. + * Use Sonata\AdminBundle\FieldDescription\TypeGuesserChain instead. + * * @final since sonata-project/admin-bundle 3.52 * * @author Bernhard Schussek diff --git a/src/Guesser/TypeGuesserInterface.php b/src/Guesser/TypeGuesserInterface.php index 8edd1be7c5..c8accd8f5b 100644 --- a/src/Guesser/TypeGuesserInterface.php +++ b/src/Guesser/TypeGuesserInterface.php @@ -17,6 +17,11 @@ use Symfony\Component\Form\Guess\TypeGuess; /** + * NEXT_MAJOR: Remove this class. + * + * @deprecated since sonata-project/admin-bundle 3.x, to be removed in 4.0. + * Use Sonata\AdminBundle\FieldDescription\TypeGuesserInterface instead. + * * @author Thomas Rabaix */ interface TypeGuesserInterface From e477c50949660ad207b8cd0c53fcbf24d5194b74 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Sun, 7 Mar 2021 23:55:17 +0100 Subject: [PATCH 6/6] Add FieldDescriptionOptions psalm type --- src/Admin/BaseFieldDescription.php | 5 +++ src/Admin/FieldDescriptionInterface.php | 42 +++++++++++++++++++ .../FieldDescriptionFactoryInterface.php | 5 +++ 3 files changed, 52 insertions(+) diff --git a/src/Admin/BaseFieldDescription.php b/src/Admin/BaseFieldDescription.php index 53be393f2f..0a3a0f7568 100644 --- a/src/Admin/BaseFieldDescription.php +++ b/src/Admin/BaseFieldDescription.php @@ -63,6 +63,8 @@ * * @author Thomas Rabaix * + * @psalm-import-type FieldDescriptionOptions from FieldDescriptionInterface + * * @method void setFieldMapping(array $fieldMapping) * @method void setAssociationMapping(array $associationMapping) * @method void setParentAssociationMappings(array $parentAssociationMappings) @@ -141,6 +143,9 @@ abstract class BaseFieldDescription implements FieldDescriptionInterface /** * NEXT_MAJOR: Remove the null default value for $name and restrict param type to `string`. + * + * @psalm-param FieldDescriptionOptions $options + * @phpstan-param array $options */ public function __construct( ?string $name = null, diff --git a/src/Admin/FieldDescriptionInterface.php b/src/Admin/FieldDescriptionInterface.php index 7b8a46d9d8..0474f6b707 100644 --- a/src/Admin/FieldDescriptionInterface.php +++ b/src/Admin/FieldDescriptionInterface.php @@ -14,10 +14,46 @@ namespace Sonata\AdminBundle\Admin; use Sonata\AdminBundle\Exception\NoValueException; +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\PropertyAccess\PropertyPathInterface; /** * @author Thomas Rabaix * + * @psalm-type FieldDescriptionOptions = array{ + * accessor?: string|callable|PropertyPathInterface, + * actions?: array, + * admin_code?: string, + * associated_property?: string, + * block_name?: string, + * catalogue?: string, + * data_transformer?: DataTransformerInterface, + * edit?: string, + * editable?: bool, + * field_name?: string, + * field_options?: array, + * field_type?: string, + * header_class?: string, + * identifier?: bool, + * inline?: string, + * label?: bool|string|null, + * link_parameters?: array, + * multiple?: bool, + * placeholder?: string, + * required?: bool, + * role?: string|string[], + * route?: array, + * safe?: bool, + * sort_field_mapping?: array, + * sort_parent_association_mappings?: array, + * sortable?: bool, + * template?: string, + * timezone?: string|\DateTimeZone, + * translation_domain?: string, + * type?: string, + * virtual_field?: bool + * } + * * @method string|null getTargetModel() * @method bool hasAdmin() * @method bool hasParent() @@ -105,6 +141,9 @@ public function setOption($name, $value); * - template. * * Then the value are copied across to the related property value + * + * @psalm-param FieldDescriptionOptions $options + * @phpstan-param array $options */ public function setOptions(array $options); @@ -112,6 +151,9 @@ public function setOptions(array $options); * Returns options. * * @return array options + * + * @psalm-return FieldDescriptionOptions + * @phpstan-return array */ public function getOptions(); diff --git a/src/FieldDescription/FieldDescriptionFactoryInterface.php b/src/FieldDescription/FieldDescriptionFactoryInterface.php index e76f8292d0..28be299bb4 100644 --- a/src/FieldDescription/FieldDescriptionFactoryInterface.php +++ b/src/FieldDescription/FieldDescriptionFactoryInterface.php @@ -15,10 +15,15 @@ use Sonata\AdminBundle\Admin\FieldDescriptionInterface; +/** + * @psalm-import-type FieldDescriptionOptions from FieldDescriptionInterface + */ interface FieldDescriptionFactoryInterface { /** * @phpstan-param class-string $class + * @psalm-param FieldDescriptionOptions $options + * @phpstan-param array $options */ public function create(string $class, string $name, array $options = []): FieldDescriptionInterface; }