diff --git a/UPGRADE-3.x.md b/UPGRADE-3.x.md index 8c7513d3..d496e5de 100644 --- a/UPGRADE-3.x.md +++ b/UPGRADE-3.x.md @@ -8,9 +8,30 @@ UPGRADE FROM 3.x to 3.x Deprecated `modelReverseTransform()` method, use `reverseTransform()` instead. +### Sonata\DoctrineMongoDBAdminBundle\Builder\ListBuilder + +Deprecated `buildActionFieldDescription()` method without replacement. + +### Sonata\DoctrineMongoDBAdminBundle\Guesser\TypeGuesser + +Deprecated `guessType()` method, you should use `guess()` method instead. + +### Sonata\DoctrineMongoDBAdminBundle\Guesser\FilterTypeGuesser + +Deprecated `guessType()` method, you should use `guess()` method instead. + +### Sonata\DoctrineMongoDBAdminBundle\Guesser\AbstractTypeGuesser + +This class has been deprecated without replacement. + +### Sonata\DoctrineMongoDBAdminBundle\Model\ModelManager + +Deprecated `getParentMetadataForProperty()` method. +Deprecated `getNewFieldDescriptionInstance()` method, you SHOULD use `FieldDescriptionFactory::create()` instead. + ### Sonata\DoctrineMongoDBAdminBundle\Filter\Filter -Deprecate passing an instance of `Sonata\AdminBundle\Datagrid\ProxyQueryInterface` +Deprecated passing an instance of `Sonata\AdminBundle\Datagrid\ProxyQueryInterface` which is not an instance of `Sonata\DoctrineMongoDBAdminBundle\Datagrid\ProxyQueryInterface` as argument 1 to the `Sonata\DoctrineMongoDBAdminBundle\Filter\Filter::filter()` method. diff --git a/src/Admin/FieldDescription.php b/src/Admin/FieldDescription.php index b1bfc991..0861ba27 100644 --- a/src/Admin/FieldDescription.php +++ b/src/Admin/FieldDescription.php @@ -33,6 +33,7 @@ public function setAssociationMapping($associationMapping) $this->associationMapping = $associationMapping; + // NEXT_MAJOR: Remove next line. $this->type = $this->type ?: $associationMapping['type']; $this->mappingType = $this->mappingType ?: $associationMapping['type']; $this->fieldName = $associationMapping['fieldName']; @@ -77,6 +78,7 @@ public function setFieldMapping($fieldMapping) $this->fieldMapping = $fieldMapping; + // NEXT_MAJOR: Remove next line. $this->type = $this->type ?: $fieldMapping['type']; $this->mappingType = $this->mappingType ?: $fieldMapping['type']; $this->fieldName = $this->fieldName ?: $fieldMapping['fieldName']; diff --git a/src/Builder/DatagridBuilder.php b/src/Builder/DatagridBuilder.php index 29e13fc0..7a1254fd 100644 --- a/src/Builder/DatagridBuilder.php +++ b/src/Builder/DatagridBuilder.php @@ -19,8 +19,9 @@ use Sonata\AdminBundle\Builder\DatagridBuilderInterface; use Sonata\AdminBundle\Datagrid\Datagrid; use Sonata\AdminBundle\Datagrid\DatagridInterface; +use Sonata\AdminBundle\FieldDescription\TypeGuesserInterface; use Sonata\AdminBundle\Filter\FilterFactoryInterface; -use Sonata\AdminBundle\Guesser\TypeGuesserInterface; +use Sonata\AdminBundle\Guesser\TypeGuesserInterface as DeprecatedTypeGuesserInterface; use Sonata\DoctrineMongoDBAdminBundle\Datagrid\Pager; use Sonata\DoctrineMongoDBAdminBundle\Model\ModelManager; use Symfony\Component\Form\Extension\Core\Type\FormType; @@ -42,7 +43,9 @@ class DatagridBuilder implements DatagridBuilderInterface protected $formFactory; /** - * @var TypeGuesserInterface + * NEXT_MAJOR: Remove DeprecatedTypeGuesserInterface type. + * + * @var DeprecatedTypeGuesserInterface|TypeGuesserInterface */ protected $guesser; @@ -54,9 +57,12 @@ class DatagridBuilder implements DatagridBuilderInterface protected $csrfTokenEnabled; /** - * @param bool $csrfTokenEnabled + * NEXT_MAJOR: Remove DeprecatedTypeGuesserInterface type and add TypeGuesserInterface to the constructor. + * + * @param DeprecatedTypeGuesserInterface|TypeGuesserInterface $guesser + * @param bool $csrfTokenEnabled */ - public function __construct(FormFactoryInterface $formFactory, FilterFactoryInterface $filterFactory, TypeGuesserInterface $guesser, $csrfTokenEnabled = true) + public function __construct(FormFactoryInterface $formFactory, FilterFactoryInterface $filterFactory, $guesser, $csrfTokenEnabled = true) { $this->formFactory = $formFactory; $this->filterFactory = $filterFactory; @@ -69,7 +75,7 @@ public function __construct(FormFactoryInterface $formFactory, FilterFactoryInte */ public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription) { - // set default values + // NEXT_MAJOR: Remove this call. $fieldDescription->setAdmin($admin); // NEXT_MAJOR: Remove the following 2 lines. @@ -78,7 +84,7 @@ public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInter // NEXT_MAJOR: Remove this block. if ($modelManager->hasMetadata($admin->getClass(), 'sonata_deprecation_mute')) { - [$metadata, $lastPropertyName, $parentAssociationMappings] = $modelManager->getParentMetadataForProperty($admin->getClass(), $fieldDescription->getName()); + [$metadata, $lastPropertyName, $parentAssociationMappings] = $modelManager->getParentMetadataForProperty($admin->getClass(), $fieldDescription->getName(), 'sonata_deprecation_mute'); // set the default field mapping if (isset($metadata->fieldMappings[$lastPropertyName])) { @@ -127,7 +133,16 @@ public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInter public function addFilter(DatagridInterface $datagrid, $type, FieldDescriptionInterface $fieldDescription, AdminInterface $admin) { if (null === $type) { - $guessType = $this->guesser->guessType($admin->getClass(), $fieldDescription->getName(), $admin->getModelManager()); + // NEXT_MAJOR: Remove the condition and keep the if part. + if ($this->guesser instanceof TypeGuesserInterface) { + $guessType = $this->guesser->guess($fieldDescription); + } else { + $guessType = $this->guesser->guessType( + $admin->getClass(), + $fieldDescription->getName(), + $admin->getModelManager() + ); + } $type = $guessType->getType(); diff --git a/src/Builder/FormContractor.php b/src/Builder/FormContractor.php index 79a79bb6..6bf67004 100644 --- a/src/Builder/FormContractor.php +++ b/src/Builder/FormContractor.php @@ -74,6 +74,7 @@ public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInter } } + // NEXT_MAJOR: Remove this block. if (!$fieldDescription->getType()) { throw new \RuntimeException(sprintf( 'Please define a type for field `%s` in `%s`', @@ -82,6 +83,7 @@ public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInter )); } + // NEXT_MAJOR: Remove this call. $fieldDescription->setAdmin($admin); $fieldDescription->setOption('edit', $fieldDescription->getOption('edit', 'standard')); diff --git a/src/Builder/ListBuilder.php b/src/Builder/ListBuilder.php index bbc0994e..82f5d425 100644 --- a/src/Builder/ListBuilder.php +++ b/src/Builder/ListBuilder.php @@ -18,7 +18,8 @@ use Sonata\AdminBundle\Admin\FieldDescriptionCollection; use Sonata\AdminBundle\Admin\FieldDescriptionInterface; use Sonata\AdminBundle\Builder\ListBuilderInterface; -use Sonata\AdminBundle\Guesser\TypeGuesserInterface; +use Sonata\AdminBundle\FieldDescription\TypeGuesserInterface; +use Sonata\AdminBundle\Guesser\TypeGuesserInterface as DeprecatedTypeGuesserInterface; use Sonata\DoctrineMongoDBAdminBundle\Model\ModelManager; /** @@ -26,11 +27,25 @@ */ class ListBuilder implements ListBuilderInterface { + /** + * NEXT_MAJOR: Remove DeprecatedTypeGuesserInterface type. + * + * @var DeprecatedTypeGuesserInterface|TypeGuesserInterface + */ protected $guesser; + /** + * @var string[] + */ protected $templates = []; - public function __construct(TypeGuesserInterface $guesser, array $templates = []) + /** + * NEXT_MAJOR: Remove DeprecatedTypeGuesserInterface type and add TypeGuesserInterface to the constructor. + * + * @param DeprecatedTypeGuesserInterface|TypeGuesserInterface $guesser + * @param string[] $templates + */ + public function __construct($guesser, array $templates = []) { $this->guesser = $guesser; $this->templates = $templates; @@ -47,7 +62,16 @@ public function getBaseList(array $options = []) public function buildField($type, FieldDescriptionInterface $fieldDescription, AdminInterface $admin) { if (null === $type) { - $guessType = $this->guesser->guessType($admin->getClass(), $fieldDescription->getName(), $admin->getModelManager()); + // NEXT_MAJOR: Remove the condition and keep the if part. + if ($this->guesser instanceof TypeGuesserInterface) { + $guessType = $this->guesser->guess($fieldDescription); + } else { + $guessType = $this->guesser->guessType( + $admin->getClass(), + $fieldDescription->getName(), + $admin->getModelManager() + ); + } $fieldDescription->setType($guessType->getType()); } else { $fieldDescription->setType($type); @@ -73,9 +97,10 @@ public function addField(FieldDescriptionCollection $list, $type, FieldDescripti public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription) { if ('_action' === $fieldDescription->getName() || 'actions' === $fieldDescription->getType()) { - $this->buildActionFieldDescription($fieldDescription); + $this->buildActionFieldDescription($fieldDescription, 'sonata_deprecation_mute'); } + // NEXT_MAJOR: Remove this call. $fieldDescription->setAdmin($admin); // NEXT_MAJOR: Remove the following 2 lines. @@ -84,7 +109,7 @@ public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInter // NEXT_MAJOR: Remove this block. if ($modelManager->hasMetadata($admin->getClass(), 'sonata_deprecation_mute')) { - [$metadata, $lastPropertyName, $parentAssociationMappings] = $modelManager->getParentMetadataForProperty($admin->getClass(), $fieldDescription->getName()); + [$metadata, $lastPropertyName, $parentAssociationMappings] = $modelManager->getParentMetadataForProperty($admin->getClass(), $fieldDescription->getName(), 'sonata_deprecation_mute'); $fieldDescription->setParentAssociationMappings($parentAssociationMappings); // set the default field mapping @@ -154,6 +179,14 @@ public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInter */ public function buildActionFieldDescription(FieldDescriptionInterface $fieldDescription) { + if ('sonata_deprecation_mute' !== (\func_get_args()[1] ?? null)) { + @trigger_error(sprintf( + 'The "%s()" method is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x and' + .' will be removed in version 4.0.', + __METHOD__ + ), \E_USER_DEPRECATED); + } + if (null === $fieldDescription->getTemplate()) { $fieldDescription->setTemplate('@SonataAdmin/CRUD/list__action.html.twig'); } diff --git a/src/Builder/ShowBuilder.php b/src/Builder/ShowBuilder.php index 5fc6bf76..a39832ac 100644 --- a/src/Builder/ShowBuilder.php +++ b/src/Builder/ShowBuilder.php @@ -18,7 +18,8 @@ use Sonata\AdminBundle\Admin\FieldDescriptionCollection; use Sonata\AdminBundle\Admin\FieldDescriptionInterface; use Sonata\AdminBundle\Builder\ShowBuilderInterface; -use Sonata\AdminBundle\Guesser\TypeGuesserInterface; +use Sonata\AdminBundle\FieldDescription\TypeGuesserInterface; +use Sonata\AdminBundle\Guesser\TypeGuesserInterface as DeprecatedTypeGuesserInterface; use Sonata\DoctrineMongoDBAdminBundle\Model\ModelManager; /** @@ -26,11 +27,25 @@ */ class ShowBuilder implements ShowBuilderInterface { + /** + * NEXT_MAJOR: Remove DeprecatedTypeGuesserInterface type. + * + * @var DeprecatedTypeGuesserInterface|TypeGuesserInterface + */ protected $guesser; + /** + * @var string[] + */ protected $templates; - public function __construct(TypeGuesserInterface $guesser, array $templates) + /** + * NEXT_MAJOR: Remove DeprecatedTypeGuesserInterface type and add TypeGuesserInterface to the constructor. + * + * @param DeprecatedTypeGuesserInterface|TypeGuesserInterface $guesser + * @param string[] $templates + */ + public function __construct($guesser, array $templates) { $this->guesser = $guesser; $this->templates = $templates; @@ -47,7 +62,16 @@ public function getBaseList(array $options = []) public function addField(FieldDescriptionCollection $list, $type, FieldDescriptionInterface $fieldDescription, AdminInterface $admin) { if (null === $type) { - $guessType = $this->guesser->guessType($admin->getClass(), $fieldDescription->getName(), $admin->getModelManager()); + // NEXT_MAJOR: Remove the condition and keep the if part. + if ($this->guesser instanceof TypeGuesserInterface) { + $guessType = $this->guesser->guess($fieldDescription); + } else { + $guessType = $this->guesser->guessType( + $admin->getClass(), + $fieldDescription->getName(), + $admin->getModelManager() + ); + } $fieldDescription->setType($guessType->getType()); } else { $fieldDescription->setType($type); @@ -64,6 +88,7 @@ public function addField(FieldDescriptionCollection $list, $type, FieldDescripti */ public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription) { + // NEXT_MAJOR: Remove this call. $fieldDescription->setAdmin($admin); // NEXT_MAJOR: Remove the following 2 lines. @@ -72,7 +97,7 @@ public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInter // NEXT_MAJOR: Remove this block. if ($modelManager->hasMetadata($admin->getClass(), 'sonata_deprecation_mute')) { - [$metadata, $lastPropertyName, $parentAssociationMappings] = $modelManager->getParentMetadataForProperty($admin->getClass(), $fieldDescription->getName()); + [$metadata, $lastPropertyName, $parentAssociationMappings] = $modelManager->getParentMetadataForProperty($admin->getClass(), $fieldDescription->getName(), 'sonata_deprecation_mute'); $fieldDescription->setParentAssociationMappings($parentAssociationMappings); // set the default field mapping diff --git a/src/FieldDescription/FieldDescriptionFactory.php b/src/FieldDescription/FieldDescriptionFactory.php new file mode 100644 index 00000000..127953c9 --- /dev/null +++ b/src/FieldDescription/FieldDescriptionFactory.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\DoctrineMongoDBAdminBundle\FieldDescription; + +use Doctrine\ODM\MongoDB\DocumentManager; +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\Persistence\ManagerRegistry; +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; +use Sonata\AdminBundle\FieldDescription\FieldDescriptionFactoryInterface; +use Sonata\DoctrineMongoDBAdminBundle\Admin\FieldDescription; + +final class FieldDescriptionFactory implements FieldDescriptionFactoryInterface +{ + /** + * @var ManagerRegistry + */ + private $registry; + + public function __construct(ManagerRegistry $registry) + { + $this->registry = $registry; + } + + 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'] = []; + } + + [$metadata, $propertyName, $parentAssociationMappings] = $this->getParentMetadataForProperty($class, $name); + + return new FieldDescription( + $name, + $options, + $metadata->fieldMappings[$propertyName] ?? [], + $metadata->associationMappings[$propertyName] ?? [], + $parentAssociationMappings, + $propertyName + ); + } + + private function getParentMetadataForProperty(string $baseClass, string $propertyFullName): array + { + $nameElements = explode('.', $propertyFullName); + $lastPropertyName = array_pop($nameElements); + $class = $baseClass; + $parentAssociationMappings = []; + + foreach ($nameElements as $nameElement) { + $metadata = $this->getMetadata($class); + $parentAssociationMappings[] = $metadata->associationMappings[$nameElement]; + $class = $metadata->getAssociationTargetClass($nameElement); + } + + return [$this->getMetadata($class), $lastPropertyName, $parentAssociationMappings]; + } + + /** + * @param class-string $class + */ + private function getMetadata(string $class): ClassMetadata + { + return $this->getDocumentManager($class)->getClassMetadata($class); + } + + /** + * @param class-string $class + * + * @throw \RuntimeException + */ + private function getDocumentManager(string $class): DocumentManager + { + $dm = $this->registry->getManagerForClass($class); + + if (!$dm instanceof DocumentManager) { + throw new \RuntimeException(sprintf('No document manager defined for class %s', $class)); + } + + return $dm; + } +} diff --git a/src/Guesser/AbstractTypeGuesser.php b/src/Guesser/AbstractTypeGuesser.php index ecd37106..a848099c 100644 --- a/src/Guesser/AbstractTypeGuesser.php +++ b/src/Guesser/AbstractTypeGuesser.php @@ -17,9 +17,16 @@ use Sonata\AdminBundle\Guesser\TypeGuesserInterface; use Sonata\DoctrineMongoDBAdminBundle\Model\ModelManager; +/** + * NEXT_MAJOR: Remove this class. + * + * @deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x and will be removed in version 4.0 + */ abstract class AbstractTypeGuesser implements TypeGuesserInterface { /** + * @deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x and will be removed in version 4.0. + * * @param string $baseClass * @param string $propertyFullName * @@ -27,8 +34,16 @@ abstract class AbstractTypeGuesser implements TypeGuesserInterface */ protected function getParentMetadataForProperty($baseClass, $propertyFullName, ModelManager $modelManager) { + if ('sonata_deprecation_mute' !== (\func_get_args()[3] ?? null)) { + @trigger_error(sprintf( + 'The "%s()" method is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x and' + .' will be removed in version 4.0.', + __METHOD__ + ), \E_USER_DEPRECATED); + } + try { - return $modelManager->getParentMetadataForProperty($baseClass, $propertyFullName); + return $modelManager->getParentMetadataForProperty($baseClass, $propertyFullName, 'sonata_deprecation_mute'); } catch (MappingException $e) { // no metadata found. return null; diff --git a/src/Guesser/FilterTypeGuesser.php b/src/Guesser/FilterTypeGuesser.php index fbd45783..696b2b74 100644 --- a/src/Guesser/FilterTypeGuesser.php +++ b/src/Guesser/FilterTypeGuesser.php @@ -16,6 +16,8 @@ use Doctrine\Bundle\MongoDBBundle\Form\Type\DocumentType; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Types\Type; +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; +use Sonata\AdminBundle\FieldDescription\TypeGuesserInterface; use Sonata\AdminBundle\Form\Type\Operator\EqualOperatorType; use Sonata\AdminBundle\Model\ModelManagerInterface; use Sonata\DoctrineMongoDBAdminBundle\Filter\BooleanFilter; @@ -34,13 +36,24 @@ use Symfony\Component\Form\Guess\TypeGuess; /** + * NEXT_MAJOR: Remove extending from AbstractTypeGuesser. + * * @final since sonata-project/doctrine-mongodb-admin-bundle 3.5. */ -class FilterTypeGuesser extends AbstractTypeGuesser +class FilterTypeGuesser extends AbstractTypeGuesser implements TypeGuesserInterface { + /** + * NEXT_MAJOR: Remove this method. + */ public function guessType($class, $property, ModelManagerInterface $modelManager) { - if (!$ret = $this->getParentMetadataForProperty($class, $property, $modelManager)) { + @trigger_error(sprintf( + 'The "%s()" method is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x and' + .' will be removed in version 4.0.', + __METHOD__ + ), \E_USER_DEPRECATED); + + if (!$ret = $this->getParentMetadataForProperty($class, $property, $modelManager, 'sonata_deprecation_mute')) { return null; } @@ -158,4 +171,126 @@ public function guessType($class, $property, ModelManagerInterface $modelManager return new TypeGuess(StringFilter::class, $options, Guess::LOW_CONFIDENCE); } } + + public function guess(FieldDescriptionInterface $fieldDescription): TypeGuess + { + $options = [ + 'parent_association_mappings' => $fieldDescription->getParentAssociationMappings(), + 'field_name' => $fieldDescription->getFieldName(), + 'field_type' => null, + 'field_options' => [], + 'options' => [], + ]; + + if ([] !== $fieldDescription->getAssociationMapping()) { + switch ($fieldDescription->getMappingType()) { + case ClassMetadata::ONE: + case ClassMetadata::MANY: + $options['operator_type'] = EqualOperatorType::class; + $options['operator_options'] = []; + + $options['field_type'] = DocumentType::class; + + // NEXT_MAJOR: Remove the if check and else part. + if (method_exists($fieldDescription, 'getTargetModel')) { + $options['field_options'] = [ + 'class' => $fieldDescription->getTargetModel(), + ]; + } else { + $options['field_options'] = [ + 'class' => $fieldDescription->getTargetEntity(), + ]; + } + + $options['field_name'] = $fieldDescription->getFieldName(); + $options['mapping_type'] = $fieldDescription->getMappingType(); + + return new TypeGuess(ModelFilter::class, $options, Guess::HIGH_CONFIDENCE); + } + } + + if ([] === $fieldDescription->getFieldMapping()) { + throw new MissingPropertyMetadataException( + $fieldDescription->getAdmin()->getClass(), + $fieldDescription->getFieldName() + ); + } + + switch ($fieldDescription->getMappingType()) { + case Type::BOOL: + case Type::BOOLEAN: + $options['field_type'] = BooleanType::class; + $options['field_options'] = []; + + return new TypeGuess(BooleanFilter::class, $options, Guess::HIGH_CONFIDENCE); + case 'datetime': + @trigger_error( + 'The datetime type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + $options['field_type'] = DateTimeType::class; + + return new TypeGuess(DateTimeFilter::class, $options, Guess::HIGH_CONFIDENCE); + case Type::TIMESTAMP: + $options['field_type'] = DateTimeType::class; + + return new TypeGuess(DateTimeFilter::class, $options, Guess::HIGH_CONFIDENCE); + case Type::DATE: + + case Type::DATE_IMMUTABLE: + $options['field_type'] = DateType::class; + + return new TypeGuess(DateFilter::class, $options, Guess::HIGH_CONFIDENCE); + case 'decimal': + @trigger_error( + 'The decimal type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + $options['field_type'] = NumberType::class; + + return new TypeGuess(NumberFilter::class, $options, Guess::MEDIUM_CONFIDENCE); + case 'bigint': + @trigger_error( + 'The bigint type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + $options['field_type'] = NumberType::class; + + return new TypeGuess(NumberFilter::class, $options, Guess::MEDIUM_CONFIDENCE); + case 'smallint': + @trigger_error( + 'The smallint type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + $options['field_type'] = NumberType::class; + + return new TypeGuess(NumberFilter::class, $options, Guess::MEDIUM_CONFIDENCE); + case Type::FLOAT: + case Type::INT: + case Type::INTEGER: + $options['field_type'] = NumberType::class; + + return new TypeGuess(NumberFilter::class, $options, Guess::MEDIUM_CONFIDENCE); + case 'text': + @trigger_error( + 'The text type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + $options['field_type'] = TextType::class; + + return new TypeGuess(StringFilter::class, $options, Guess::MEDIUM_CONFIDENCE); + case Type::ID: + case Type::STRING: + $options['field_type'] = TextType::class; + + return new TypeGuess(StringFilter::class, $options, Guess::MEDIUM_CONFIDENCE); + default: + return new TypeGuess(StringFilter::class, $options, Guess::LOW_CONFIDENCE); + } + } } diff --git a/src/Guesser/TypeGuesser.php b/src/Guesser/TypeGuesser.php index 25357bde..fb9baa6d 100755 --- a/src/Guesser/TypeGuesser.php +++ b/src/Guesser/TypeGuesser.php @@ -16,18 +16,30 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Types\Type; use Sonata\AdminBundle\Admin\FieldDescriptionInterface; +use Sonata\AdminBundle\FieldDescription\TypeGuesserInterface; use Sonata\AdminBundle\Model\ModelManagerInterface; use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\TypeGuess; /** + * NEXT_MAJOR: Remove extending from AbstractTypeGuesser. + * * @final since sonata-project/doctrine-mongodb-admin-bundle 3.5. */ -class TypeGuesser extends AbstractTypeGuesser +class TypeGuesser extends AbstractTypeGuesser implements TypeGuesserInterface { + /** + * NEXT_MAJOR: Remove this method. + */ public function guessType($class, $property, ModelManagerInterface $modelManager) { - if (!$ret = $this->getParentMetadataForProperty($class, $property, $modelManager)) { + @trigger_error(sprintf( + 'The "%s()" method is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x and' + .' will be removed in version 4.0.', + __METHOD__ + ), \E_USER_DEPRECATED); + + if (!$ret = $this->getParentMetadataForProperty($class, $property, $modelManager, 'sonata_deprecation_mute')) { // NEXT_MAJOR: Remove next line and uncomment the other following one. return new TypeGuess('text', [], Guess::LOW_CONFIDENCE); // return new TypeGuess(FieldDescriptionInterface::TYPE_STRING, [], Guess::LOW_CONFIDENCE); @@ -139,4 +151,117 @@ public function guessType($class, $property, ModelManagerInterface $modelManager // return new TypeGuess(FieldDescriptionInterface::TYPE_STRING, [], Guess::MEDIUM_CONFIDENCE); } } + + public function guess(FieldDescriptionInterface $fieldDescription): TypeGuess + { + $fieldMapping = $fieldDescription->getFieldMapping(); + + if ([] === $fieldMapping) { + // NEXT_MAJOR: Remove next line and uncomment the other following one. + return new TypeGuess('text', [], Guess::LOW_CONFIDENCE); + // return new TypeGuess(FieldDescriptionInterface::TYPE_STRING, [], Guess::LOW_CONFIDENCE); + } + + if ([] !== $fieldDescription->getAssociationMapping()) { + switch ($fieldDescription->getMappingType()) { + case ClassMetadata::ONE: + return new TypeGuess('mongo_one', [], Guess::HIGH_CONFIDENCE); + + case ClassMetadata::MANY: + return new TypeGuess('mongo_many', [], Guess::HIGH_CONFIDENCE); + } + } + + switch ($fieldDescription->getMappingType()) { + case Type::COLLECTION: + case Type::HASH: + return new TypeGuess(FieldDescriptionInterface::TYPE_ARRAY, [], Guess::HIGH_CONFIDENCE); + case 'array': + @trigger_error( + 'The array type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + return new TypeGuess(FieldDescriptionInterface::TYPE_ARRAY, [], Guess::HIGH_CONFIDENCE); + case Type::BOOL: + case Type::BOOLEAN: + return new TypeGuess(FieldDescriptionInterface::TYPE_BOOLEAN, [], Guess::HIGH_CONFIDENCE); + case 'datetime': + @trigger_error( + 'The datetime type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + return new TypeGuess(FieldDescriptionInterface::TYPE_DATETIME, [], Guess::HIGH_CONFIDENCE); + case 'vardatetime': + @trigger_error( + 'The vardatetime type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + return new TypeGuess(FieldDescriptionInterface::TYPE_DATETIME, [], Guess::HIGH_CONFIDENCE); + case 'datetimetz': + @trigger_error( + 'The datetimetz type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + return new TypeGuess(FieldDescriptionInterface::TYPE_DATETIME, [], Guess::HIGH_CONFIDENCE); + case Type::TIMESTAMP: + return new TypeGuess(FieldDescriptionInterface::TYPE_DATETIME, [], Guess::HIGH_CONFIDENCE); + case Type::DATE: + case Type::DATE_IMMUTABLE: + return new TypeGuess(FieldDescriptionInterface::TYPE_DATE, [], Guess::HIGH_CONFIDENCE); + case 'decimal': + @trigger_error( + 'The decimal type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + return new TypeGuess('number', [], Guess::MEDIUM_CONFIDENCE); + case Type::FLOAT: + // NEXT_MAJOR: Remove next line and uncomment the following one. + return new TypeGuess('number', [], Guess::MEDIUM_CONFIDENCE); + // return new TypeGuess(FieldDescriptionInterface::TYPE_FLOAT, [], Guess::MEDIUM_CONFIDENCE); + case Type::INTEGER: + case Type::INT: + return new TypeGuess(FieldDescriptionInterface::TYPE_INTEGER, [], Guess::MEDIUM_CONFIDENCE); + case 'bigint': + @trigger_error( + 'The bigint type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + return new TypeGuess(FieldDescriptionInterface::TYPE_INTEGER, [], Guess::MEDIUM_CONFIDENCE); + case 'smallint': + @trigger_error( + 'The smallint type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + return new TypeGuess(FieldDescriptionInterface::TYPE_INTEGER, [], Guess::MEDIUM_CONFIDENCE); + case Type::STRING: + // NEXT_MAJOR: Remove next line and uncomment the following one. + return new TypeGuess('text', [], Guess::MEDIUM_CONFIDENCE); + // return new TypeGuess(FieldDescriptionInterface::TYPE_STRING, [], Guess::MEDIUM_CONFIDENCE); + case 'text': + @trigger_error( + 'The text type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + return new TypeGuess('textarea', [], Guess::MEDIUM_CONFIDENCE); + case 'time': + @trigger_error( + 'The time type is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.4, to be removed in 4.0.'. + \E_USER_DEPRECATED + ); + + return new TypeGuess('time', [], Guess::HIGH_CONFIDENCE); + default: + // NEXT_MAJOR: Remove next line and uncomment the following one. + return new TypeGuess('text', [], Guess::LOW_CONFIDENCE); + // return new TypeGuess(FieldDescriptionInterface::TYPE_STRING, [], Guess::MEDIUM_CONFIDENCE); + } + } } diff --git a/src/Model/ModelManager.php b/src/Model/ModelManager.php index 764ade7e..e0eba584 100755 --- a/src/Model/ModelManager.php +++ b/src/Model/ModelManager.php @@ -93,6 +93,8 @@ public function getMetadata($class) } /** + * @deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x and will be removed in version 4.0. + * * Returns the model's metadata holding the fully qualified property, and the last * property name. * @@ -110,6 +112,14 @@ public function getMetadata($class) */ public function getParentMetadataForProperty($baseClass, $propertyFullName) { + if ('sonata_deprecation_mute' !== (\func_get_args()[2] ?? null)) { + @trigger_error(sprintf( + 'The "%s()" method is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x and' + .' will be removed in version 4.0.', + __METHOD__ + ), \E_USER_DEPRECATED); + } + $nameElements = explode('.', $propertyFullName); $lastPropertyName = array_pop($nameElements); $class = $baseClass; @@ -148,9 +158,19 @@ public function hasMetadata($class) /** * @psalm-suppress InvalidArgument + * + * @deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x and will be removed in version 4.0. */ public function getNewFieldDescriptionInstance($class, $name, array $options = []) { + if ('sonata_deprecation_mute' !== (\func_get_args()[3] ?? null)) { + @trigger_error(sprintf( + 'The "%s()" method is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x and' + .' will be removed in version 4.0.', + __METHOD__ + ), \E_USER_DEPRECATED); + } + if (!\is_string($name)) { throw new \RuntimeException('The name argument must be a string'); } @@ -170,7 +190,8 @@ public function getNewFieldDescriptionInstance($class, $name, array $options = [ $options, $metadata->fieldMappings[$propertyName] ?? [], $metadata->associationMappings[$propertyName] ?? [], - $parentAssociationMappings + $parentAssociationMappings, + $propertyName ); } diff --git a/src/Resources/config/doctrine_mongodb.php b/src/Resources/config/doctrine_mongodb.php index 7797c617..994f91af 100644 --- a/src/Resources/config/doctrine_mongodb.php +++ b/src/Resources/config/doctrine_mongodb.php @@ -17,6 +17,7 @@ use Sonata\DoctrineMongoDBAdminBundle\Builder\ListBuilder; use Sonata\DoctrineMongoDBAdminBundle\Builder\ShowBuilder; use Sonata\DoctrineMongoDBAdminBundle\Exporter\DataSource; +use Sonata\DoctrineMongoDBAdminBundle\FieldDescription\FieldDescriptionFactory; use Sonata\DoctrineMongoDBAdminBundle\Guesser\FilterTypeGuesser; use Sonata\DoctrineMongoDBAdminBundle\Guesser\TypeGuesser; use Sonata\DoctrineMongoDBAdminBundle\Model\ModelManager; @@ -94,5 +95,10 @@ ]) ->set('sonata.admin.data_source.doctrine_mongodb', DataSource::class) + + ->set('sonata.admin.field_description_factory.doctrine_mongodb', FieldDescriptionFactory::class) + ->args([ + new ReferenceConfigurator('doctrine_mongodb'), + ]) ; }; diff --git a/tests/Builder/DatagridBuilderTest.php b/tests/Builder/DatagridBuilderTest.php index 918cd0fd..ecedaf15 100644 --- a/tests/Builder/DatagridBuilderTest.php +++ b/tests/Builder/DatagridBuilderTest.php @@ -21,8 +21,8 @@ use Sonata\AdminBundle\Datagrid\DatagridInterface; use Sonata\AdminBundle\Datagrid\Pager; use Sonata\AdminBundle\Datagrid\ProxyQueryInterface; +use Sonata\AdminBundle\FieldDescription\TypeGuesserInterface; use Sonata\AdminBundle\Filter\FilterFactoryInterface; -use Sonata\AdminBundle\Guesser\TypeGuesserInterface; use Sonata\AdminBundle\Translator\FormLabelTranslatorStrategy; use Sonata\DoctrineMongoDBAdminBundle\Admin\FieldDescription; use Sonata\DoctrineMongoDBAdminBundle\Builder\DatagridBuilder; @@ -176,7 +176,7 @@ public function testAddFilterNoType(): void $fieldDescription = new FieldDescription('test'); - $this->typeGuesser->method('guessType')->willReturn($guessType); + $this->typeGuesser->method('guess')->willReturn($guessType); $this->metadataFactory ->expects($this->once()) diff --git a/tests/Builder/ListBuilderTest.php b/tests/Builder/ListBuilderTest.php index 5c80a13c..aeaf2a18 100644 --- a/tests/Builder/ListBuilderTest.php +++ b/tests/Builder/ListBuilderTest.php @@ -18,7 +18,7 @@ use Sonata\AdminBundle\Admin\AbstractAdmin; use Sonata\AdminBundle\Admin\AdminInterface; use Sonata\AdminBundle\Admin\FieldDescriptionInterface; -use Sonata\AdminBundle\Guesser\TypeGuesserInterface; +use Sonata\AdminBundle\FieldDescription\TypeGuesserInterface; use Sonata\DoctrineMongoDBAdminBundle\Admin\FieldDescription; use Sonata\DoctrineMongoDBAdminBundle\Builder\ListBuilder; use Sonata\DoctrineMongoDBAdminBundle\Tests\AbstractModelManagerTestCase; @@ -84,7 +84,7 @@ public function testAddListActionField(): void public function testCorrectFixedActionsFieldType(): void { $this->typeGuesser - ->method('guessType') + ->method('guess') ->willReturn( new TypeGuess('actions', [], Guess::LOW_CONFIDENCE) ); diff --git a/tests/Builder/ShowBuilderTest.php b/tests/Builder/ShowBuilderTest.php index c07d2721..1bf9013b 100644 --- a/tests/Builder/ShowBuilderTest.php +++ b/tests/Builder/ShowBuilderTest.php @@ -19,7 +19,7 @@ use Sonata\AdminBundle\Admin\AdminInterface; use Sonata\AdminBundle\Admin\FieldDescriptionCollection; use Sonata\AdminBundle\Admin\FieldDescriptionInterface; -use Sonata\AdminBundle\Guesser\TypeGuesserInterface; +use Sonata\AdminBundle\FieldDescription\TypeGuesserInterface; use Sonata\DoctrineMongoDBAdminBundle\Admin\FieldDescription; use Sonata\DoctrineMongoDBAdminBundle\Builder\ShowBuilder; use Sonata\DoctrineMongoDBAdminBundle\Tests\AbstractModelManagerTestCase; @@ -78,7 +78,7 @@ public function testAddFieldNoType(): void $typeGuess->method('getType')->willReturn('fakeType'); - $this->guesser->method('guessType')->willReturn($typeGuess); + $this->guesser->method('guess')->willReturn($typeGuess); $this->metadataFactory->method('hasMetadataFor')->willReturn(false); diff --git a/tests/FieldDescription/FieldDescriptionFactoryTest.php b/tests/FieldDescription/FieldDescriptionFactoryTest.php new file mode 100644 index 00000000..91d0989e --- /dev/null +++ b/tests/FieldDescription/FieldDescriptionFactoryTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\DoctrineMongoDBAdminBundle\Tests\FieldDescription; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Types\Type; +use Sonata\DoctrineMongoDBAdminBundle\FieldDescription\FieldDescriptionFactory; +use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\ContainerDocument; + +final class FieldDescriptionFactoryTest extends RegistryTestCase +{ + public function testCreate(): void + { + $fieldDescriptionFactory = new FieldDescriptionFactory($this->registry); + + $fieldDescription = $fieldDescriptionFactory->create(ContainerDocument::class, 'plainField'); + + $this->assertSame('edit', $fieldDescription->getOption('route')['name']); + + $fieldDescription = $fieldDescriptionFactory->create(ContainerDocument::class, 'plainField'); + $this->assertSame(Type::INT, $fieldDescription->getMappingType()); + + $fieldDescription = $fieldDescriptionFactory->create(ContainerDocument::class, 'associatedDocument.plainField'); + $this->assertSame(Type::INT, $fieldDescription->getMappingType()); + + $fieldDescription = $fieldDescriptionFactory->create(ContainerDocument::class, 'embeddedDocument.plainField'); + $this->assertSame(Type::BOOL, $fieldDescription->getMappingType()); + + $fieldDescription = $fieldDescriptionFactory->create(ContainerDocument::class, 'embeddedDocument'); + $this->assertSame(ClassMetadata::ONE, $fieldDescription->getMappingType()); + + $fieldDescription = $fieldDescriptionFactory->create(ContainerDocument::class, 'embeddedDocument'); + $this->assertNotSame([], $fieldDescription->getAssociationMapping()); + } +} diff --git a/tests/FieldDescription/FilterTypeGuesserTest.php b/tests/FieldDescription/FilterTypeGuesserTest.php new file mode 100644 index 00000000..a1672592 --- /dev/null +++ b/tests/FieldDescription/FilterTypeGuesserTest.php @@ -0,0 +1,205 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\DoctrineMongoDBAdminBundle\Tests\FieldDescription; + +use Doctrine\Bundle\MongoDBBundle\Form\Type\DocumentType; +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Types\Type; +use Sonata\AdminBundle\Admin\AdminInterface; +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; +use Sonata\AdminBundle\Form\Type\Operator\EqualOperatorType; +use Sonata\DoctrineMongoDBAdminBundle\FieldDescription\FieldDescriptionFactory; +use Sonata\DoctrineMongoDBAdminBundle\Filter\BooleanFilter; +use Sonata\DoctrineMongoDBAdminBundle\Filter\DateFilter; +use Sonata\DoctrineMongoDBAdminBundle\Filter\DateTimeFilter; +use Sonata\DoctrineMongoDBAdminBundle\Filter\ModelFilter; +use Sonata\DoctrineMongoDBAdminBundle\Filter\NumberFilter; +use Sonata\DoctrineMongoDBAdminBundle\Filter\StringFilter; +use Sonata\DoctrineMongoDBAdminBundle\Guesser\FilterTypeGuesser; +use Sonata\DoctrineMongoDBAdminBundle\Model\MissingPropertyMetadataException; +use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\AssociatedDocument; +use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\ContainerDocument; +use Sonata\Form\Type\BooleanType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Guess\Guess; + +final class FilterTypeGuesserTest extends RegistryTestCase +{ + /** + * @var FilterTypeGuesser + */ + private $guesser; + + protected function setUp(): void + { + parent::setUp(); + + $this->guesser = new FilterTypeGuesser(); + } + + public function testThrowsOnMissingField(): void + { + $fieldDescription = $this->createStub(FieldDescriptionInterface::class); + $fieldDescription + ->method('getAssociationMapping') + ->willReturn([]); + + $fieldDescription + ->method('getFieldMapping') + ->willReturn([]); + + $fieldDescription + ->method('getFieldName') + ->willReturn('nonExisting'); + + $admin = $this->createStub(AdminInterface::class); + $admin + ->method('getClass') + ->willReturn(\stdClass::class); + + $fieldDescription + ->method('getAdmin') + ->willReturn($admin); + + $this->expectException(MissingPropertyMetadataException::class); + $this->guesser->guess($fieldDescription); + } + + public function testGuessTypeWithAssociation(): void + { + $className = ContainerDocument::class; + $property = 'associatedDocument'; + $parentAssociation = []; + $targetDocument = AssociatedDocument::class; + + $fieldDescriptionFactory = new FieldDescriptionFactory($this->registry); + + $fieldDescription = $fieldDescriptionFactory->create($className, $property); + + $result = $this->guesser->guess($fieldDescription); + + $options = $result->getOptions(); + + $this->assertSame(ModelFilter::class, $result->getType()); + $this->assertSame(Guess::HIGH_CONFIDENCE, $result->getConfidence()); + $this->assertSame($parentAssociation, $options['parent_association_mappings']); + $this->assertSame(ClassMetadata::ONE, $options['mapping_type']); + $this->assertSame(EqualOperatorType::class, $options['operator_type']); + $this->assertSame([], $options['operator_options']); + $this->assertSame($property, $options['field_name']); + $this->assertSame(DocumentType::class, $options['field_type']); + $this->assertSame($targetDocument, $options['field_options']['class']); + } + + /** + * @dataProvider noAssociationData + */ + public function testGuessTypeNoAssociation(string $type, string $resultType, int $confidence, ?string $fieldType = null): void + { + $property = 'fakeProperty'; + + $fieldDescription = $this->createStub(FieldDescriptionInterface::class); + $fieldDescription + ->method('getMappingType') + ->willReturn($type); + + $fieldDescription + ->method('getFieldName') + ->willReturn($property); + + $fieldDescription + ->method('getFieldMapping') + ->willReturn([$property => ['fieldName' => $property]]); + + $fieldDescription + ->method('getAssociationMapping') + ->willReturn([]); + + $result = $this->guesser->guess($fieldDescription); + + $options = $result->getOptions(); + + $this->assertSame($resultType, $result->getType()); + $this->assertSame($confidence, $result->getConfidence()); + $this->assertSame([], $options['options']); + $this->assertSame([], $options['field_options']); + + if ($fieldType) { + $this->assertSame($fieldType, $options['field_type']); + } + } + + public function noAssociationData(): iterable + { + return [ + Type::BOOLEAN => [ + 'boolean', + BooleanFilter::class, + Guess::HIGH_CONFIDENCE, + BooleanType::class, + ], + 'datetime' => [ + 'datetime', + DateTimeFilter::class, + Guess::HIGH_CONFIDENCE, + ], + Type::TIMESTAMP => [ + 'datetime', + DateTimeFilter::class, + Guess::HIGH_CONFIDENCE, + ], + Type::DATE => [ + 'date', + DateFilter::class, + Guess::HIGH_CONFIDENCE, + ], + 'decimal' => [ + 'decimal', + NumberFilter::class, + Guess::MEDIUM_CONFIDENCE, + NumberType::class, + ], + Type::FLOAT => [ + 'float', + NumberFilter::class, + Guess::MEDIUM_CONFIDENCE, + NumberType::class, + ], + Type::INT => [ + 'int', + NumberFilter::class, + Guess::MEDIUM_CONFIDENCE, + NumberType::class, + ], + Type::STRING => [ + 'string', + StringFilter::class, + Guess::MEDIUM_CONFIDENCE, + TextType::class, + ], + 'text' => [ + 'text', + StringFilter::class, + Guess::MEDIUM_CONFIDENCE, + TextType::class, + ], + 'somefake' => [ + 'somefake', + StringFilter::class, + Guess::LOW_CONFIDENCE, + ], + ]; + } +} diff --git a/tests/FieldDescription/RegistryTestCase.php b/tests/FieldDescription/RegistryTestCase.php new file mode 100644 index 00000000..33324654 --- /dev/null +++ b/tests/FieldDescription/RegistryTestCase.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\DoctrineMongoDBAdminBundle\Tests\FieldDescription; + +use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\ODM\MongoDB\DocumentManager; +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver; +use Doctrine\Persistence\ManagerRegistry; +use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; +use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\AssociatedDocument; +use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\ContainerDocument; +use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\EmbeddedDocument; + +abstract class RegistryTestCase extends TestCase +{ + /** + * @var Stub&ManagerRegistry + */ + protected $registry; + + protected function setUp(): void + { + $this->registry = $this->createStub(ManagerRegistry::class); + + $containerDocumentClass = ContainerDocument::class; + $associatedDocumentClass = AssociatedDocument::class; + $embeddedDocumentClass = EmbeddedDocument::class; + + $dm = $this->createStub(DocumentManager::class); + + $this->registry + ->method('getManagerForClass') + ->willReturn($dm); + + $containerDocumentMetadata = $this->getMetadataForDocumentWithAnnotations($containerDocumentClass); + $associatedDocumentMetadata = $this->getMetadataForDocumentWithAnnotations($associatedDocumentClass); + $embeddedDocumentMetadata = $this->getMetadataForDocumentWithAnnotations($embeddedDocumentClass); + + $dm + ->method('getClassMetadata') + ->willReturnMap( + [ + [$containerDocumentClass, $containerDocumentMetadata], + [$embeddedDocumentClass, $embeddedDocumentMetadata], + [$associatedDocumentClass, $associatedDocumentMetadata], + ] + ); + } + + private function getMetadataForDocumentWithAnnotations(string $class): ClassMetadata + { + $classMetadata = new ClassMetadata($class); + $reader = new AnnotationReader(); + + $annotationDriver = new AnnotationDriver($reader); + $annotationDriver->loadMetadataForClass($class, $classMetadata); + + return $classMetadata; + } +} diff --git a/tests/FieldDescription/TypeGuesserTest.php b/tests/FieldDescription/TypeGuesserTest.php new file mode 100644 index 00000000..037ffa2a --- /dev/null +++ b/tests/FieldDescription/TypeGuesserTest.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\DoctrineMongoDBAdminBundle\Tests\FieldDescription; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Types\Type; +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; +use Sonata\DoctrineMongoDBAdminBundle\Guesser\TypeGuesser; +use Sonata\DoctrineMongoDBAdminBundle\Tests\AbstractModelManagerTestCase; +use Symfony\Component\Form\Guess\Guess; + +/** + * @author Marko Kunic + */ +final class TypeGuesserTest extends AbstractModelManagerTestCase +{ + /** + * @var TypeGuesser + */ + private $guesser; + + protected function setUp(): void + { + parent::setUp(); + + $this->guesser = new TypeGuesser(); + } + + /** + * @dataProvider associationData + */ + public function testGuessTypeWithAssociation(string $mappingType, string $type): void + { + $fieldDescription = $this->createStub(FieldDescriptionInterface::class); + $fieldDescription + ->method('getMappingType') + ->willReturn($mappingType); + + $fieldDescription + ->method('getAssociationMapping') + ->willReturn(['something']); + + $result = $this->guesser->guess($fieldDescription); + + $this->assertSame($type, $result->getType()); + $this->assertSame(Guess::HIGH_CONFIDENCE, $result->getConfidence()); + } + + public function associationData(): array + { + return [ + 'many-to-one' => [ + ClassMetadata::ONE, + 'mongo_one', + ], + 'one-to-many' => [ + ClassMetadata::MANY, + 'mongo_many', + ], + ]; + } + + /** + * @dataProvider noAssociationData + */ + public function testGuessTypeNoAssociation(string $type, string $resultType, int $confidence): void + { + $fieldDescription = $this->createStub(FieldDescriptionInterface::class); + $fieldDescription + ->method('getMappingType') + ->willReturn($type); + + $fieldDescription + ->method('getAssociationMapping') + ->willReturn([]); + + $result = $this->guesser->guess($fieldDescription); + + $this->assertSame($resultType, $result->getType()); + $this->assertSame($confidence, $result->getConfidence()); + } + + public function noAssociationData(): array + { + return [ + 'collection' => [ + Type::COLLECTION, + 'array', + Guess::HIGH_CONFIDENCE, + ], + 'hash' => [ + Type::HASH, + 'array', + Guess::HIGH_CONFIDENCE, + ], + 'bool' => [ + Type::BOOL, + 'boolean', + Guess::HIGH_CONFIDENCE, + ], + 'timestamp' => [ + Type::TIMESTAMP, + 'datetime', + Guess::HIGH_CONFIDENCE, + ], + 'date' => [ + Type::DATE, + 'date', + Guess::HIGH_CONFIDENCE, + ], + 'date_immutable' => [ + Type::DATE_IMMUTABLE, + 'date', + Guess::HIGH_CONFIDENCE, + ], + 'float' => [ + Type::FLOAT, + 'number', + Guess::MEDIUM_CONFIDENCE, + ], + 'integer' => [ + Type::INT, + 'integer', + Guess::MEDIUM_CONFIDENCE, + ], + 'string' => [ + Type::STRING, + 'text', + Guess::MEDIUM_CONFIDENCE, + ], + 'somefake' => [ + 'somefake', + 'text', + Guess::LOW_CONFIDENCE, + ], + ]; + } +} diff --git a/tests/Guesser/FilterTypeGuesserTest.php b/tests/Guesser/FilterTypeGuesserTest.php index badce61e..cb96a76f 100644 --- a/tests/Guesser/FilterTypeGuesserTest.php +++ b/tests/Guesser/FilterTypeGuesserTest.php @@ -35,6 +35,11 @@ use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Guess\Guess; +/** + * NEXT_MAJOR: Remove this file. + * + * @group legacy + */ class FilterTypeGuesserTest extends AbstractModelManagerTestCase { /** diff --git a/tests/Guesser/TypeGuesserTest.php b/tests/Guesser/TypeGuesserTest.php index e1ed27b2..e3f86d4b 100644 --- a/tests/Guesser/TypeGuesserTest.php +++ b/tests/Guesser/TypeGuesserTest.php @@ -21,6 +21,10 @@ use Symfony\Component\Form\Guess\Guess; /** + * NEXT_MAJOR: Remove this class. + * + * @group legacy + * * @author Marko Kunic */ final class TypeGuesserTest extends AbstractModelManagerTestCase diff --git a/tests/Model/ModelManagerTest.php b/tests/Model/ModelManagerTest.php index ae403b88..18be8466 100644 --- a/tests/Model/ModelManagerTest.php +++ b/tests/Model/ModelManagerTest.php @@ -184,6 +184,11 @@ public function testSortParameters(): void $this->assertSame('field3sortBy', $parameters['filter']['_sort_by']); } + /** + * NEXT_MAJOR: Remove this test. + * + * @group legacy + */ public function testGetParentMetadataForProperty(): void { $containerDocumentClass = ContainerDocument::class; @@ -411,6 +416,11 @@ public function testGetUrlSafeIdentifierNull(): void $this->assertNull($model->getNormalizedIdentifier(null)); } + /** + * NEXT_MAJOR: Remove this test. + * + * @group legacy + */ public function testGetNewFieldDescriptionInstanceCreatesAFieldDescription(): void { $dm = $this->createStub(DocumentManager::class);