diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md
index 18f3910a40..f49dcab00b 100644
--- a/UPGRADE-4.0.md
+++ b/UPGRADE-4.0.md
@@ -9,7 +9,7 @@ If you still need it, please set it up on your own!
Frontend dependencies are handled with NPM. Bower is not used anymore.
-A lot of assets that were previously public are handled with NPM and placed in a private `node_modules/` directory.
+A lot of assets that were previously public are handled with NPM and placed in a private `node_modules/` directory.
From these dependencies, only the necessary files are exposed publicly through Webpack Encore.
Please check the `src/Resources/public/dist` and the documentation to see the used CSS, JavaScript, images and fonts.
@@ -173,6 +173,19 @@ When there is no searchable filters, `SearchHandler::search()` returns `null`. P
## Sonata\AdminBundle\Controller\CRUDController
When the service `security.csrf.token_manager` is not available, `getCsrfToken()` returns `null`. Previously, it was returning `false`.
+## FilterInterface
+
+The type for argument 4 in `apply()` method has been changed from `array` to `Sonata\AdminBundle\Filter\Model\FilterData`.
+
+Before:
+```php
+public function apply(ProxyQueryInterface $query, array $filterData): void;
+```
+After:
+```php
+public function apply(ProxyQueryInterface $query, FilterData $filterData): void;
+```
+
## FormMapper labels
The form label are now correctly using the label translator strategy for field with `.`
(which won't be replaced by `__`). For instance, with the underscore label strategy, the
diff --git a/docs/reference/action_list.rst b/docs/reference/action_list.rst
index 9a64b51294..84841d95a1 100644
--- a/docs/reference/action_list.rst
+++ b/docs/reference/action_list.rst
@@ -537,6 +537,7 @@ If you have the **SonataDoctrineORMAdminBundle** installed you can use the
``CallbackFilter`` filter type e.g. for creating a full text filter::
use Sonata\AdminBundle\Datagrid\DatagridMapper;
+ use Sonata\AdminBundle\Filter\Model\FilterData;
final class UserAdmin extends Sonata\UserBundle\Admin\Model\UserAdmin
{
@@ -549,17 +550,17 @@ If you have the **SonataDoctrineORMAdminBundle** installed you can use the
]);
}
- public function getFullTextFilter(ProxyQueryInterface $query, string $alias, string $field, array $data): void
+ public function getFullTextFilter(ProxyQueryInterface $query, string $alias, string $field, FilterData $data): void
{
- if (!$data['value']) {
+ if (!$data->hasValue()) {
return false;
}
// Use `andWhere` instead of `where` to prevent overriding existing `where` conditions
$query->andWhere($query->expr()->orX(
- $query->expr()->like($alias.'.username', $query->expr()->literal('%' . $data['value'] . '%')),
- $query->expr()->like($alias.'.firstName', $query->expr()->literal('%' . $data['value'] . '%')),
- $query->expr()->like($alias.'.lastName', $query->expr()->literal('%' . $data['value'] . '%'))
+ $query->expr()->like($alias.'.username', $query->expr()->literal('%' . $data->getValue() . '%')),
+ $query->expr()->like($alias.'.firstName', $query->expr()->literal('%' . $data->getValue() . '%')),
+ $query->expr()->like($alias.'.lastName', $query->expr()->literal('%' . $data->getValue() . '%'))
));
return true;
@@ -571,21 +572,22 @@ The callback function should return a boolean indicating whether it is active.
You can also get the filter type which can be helpful to change the operator
type of your condition(s)::
+ use Sonata\AdminBundle\Filter\Model\FilterData;
use Sonata\Form\Type\EqualType;
final class UserAdmin extends Sonata\UserBundle\Admin\Model\UserAdmin
{
- public function getFullTextFilter(ProxyQueryInterface $query, string $alias, string $field, array $data): void
+ public function getFullTextFilter(ProxyQueryInterface $query, string $alias, string $field, FilterData $data): void
{
- if (!$data['value']) {
+ if (!$data->hasValue()) {
return;
}
- $operator = $data['type'] == EqualType::TYPE_IS_EQUAL ? '=' : '!=';
+ $operator = $data->isType(EqualType::TYPE_IS_EQUAL) ? '=' : '!=';
$query
->andWhere($alias.'.username '.$operator.' :username')
- ->setParameter('username', $data['value'])
+ ->setParameter('username', $data->getValue())
;
return true;
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index fe00546185..ed5a5f1995 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -9,3 +9,7 @@ parameters:
- # This error is made on purpose for php version < 8
message: '#^Method Sonata\\AdminBundle\\Tests\\Fixtures\\Entity\\FooToStringNull\:\:__toString\(\) should return string but returns null\.$#'
path: tests/Fixtures/Entity/FooToStringNull.php
+ - # It is on purpose because it throws an exception
+ message: "#^Call to static method Sonata\\\\AdminBundle\\\\Filter\\\\Model\\\\FilterData\\:\\:fromArray\\(\\) on a separate line has no effect\\.$#"
+ count: 1
+ path: tests/Filter/Model/FilterDataTest.php
diff --git a/psalm.xml b/psalm.xml
index ce14de2ede..c504cfcbe6 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -16,5 +16,11 @@
+
+
+
+
+
+
diff --git a/src/Filter/FilterInterface.php b/src/Filter/FilterInterface.php
index 8c2a1a8930..5817a6052d 100644
--- a/src/Filter/FilterInterface.php
+++ b/src/Filter/FilterInterface.php
@@ -14,6 +14,7 @@
namespace Sonata\AdminBundle\Filter;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
+use Sonata\AdminBundle\Filter\Model\FilterData;
/**
* @author Thomas Rabaix
@@ -24,10 +25,7 @@ interface FilterInterface
public const CONDITION_AND = 'AND';
- /**
- * @phpstan-param array{type?: int|null, value?: mixed} $filterData
- */
- public function apply(ProxyQueryInterface $query, array $filterData): void;
+ public function apply(ProxyQueryInterface $query, FilterData $filterData): void;
/**
* @throws \LogicException if the filter is not initialized
diff --git a/src/Filter/Model/FilterData.php b/src/Filter/Model/FilterData.php
new file mode 100644
index 0000000000..2f882c5360
--- /dev/null
+++ b/src/Filter/Model/FilterData.php
@@ -0,0 +1,106 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Filter\Model;
+
+/**
+ * @psalm-immutable
+ */
+final class FilterData
+{
+ /**
+ * @var ?int
+ */
+ private $type;
+
+ /**
+ * @var mixed
+ */
+ private $value;
+
+ /**
+ * @var bool
+ */
+ private $hasValue;
+
+ private function __construct()
+ {
+ $this->hasValue = false;
+ }
+
+ /**
+ * @psalm-pure
+ *
+ * @phpstan-param array{type?: int|numeric-string|null, value?: mixed} $data
+ */
+ public static function fromArray(array $data): self
+ {
+ $filterData = new self();
+
+ if (isset($data['type'])) {
+ if (!\is_int($data['type']) && (!\is_string($data['type']) || !is_numeric($data['type']))) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The "type" parameter MUST be of type "integer" or "null", %s given.',
+ \is_object($data['type']) ? 'instance of "'.\get_class($data['type']).'"' : '"'.\gettype($data['type']).'"'
+ ));
+ }
+
+ $filterData->type = (int) $data['type'];
+ }
+
+ if (\array_key_exists('value', $data)) {
+ $filterData->value = $data['value'];
+ $filterData->hasValue = true;
+ }
+
+ return $filterData;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getValue()
+ {
+ if (!$this->hasValue) {
+ throw new \LogicException('The FilterData object does not have a value.');
+ }
+
+ return $this->value;
+ }
+
+ /**
+ * @param mixed $value
+ */
+ public function changeValue($value): self
+ {
+ return self::fromArray([
+ 'type' => $this->getType(),
+ 'value' => $value,
+ ]);
+ }
+
+ public function getType(): ?int
+ {
+ return $this->type;
+ }
+
+ public function isType(int $type): bool
+ {
+ return $this->type === $type;
+ }
+
+ public function hasValue(): bool
+ {
+ return $this->hasValue;
+ }
+}
diff --git a/src/Form/DataTransformer/FilterDataTransformer.php b/src/Form/DataTransformer/FilterDataTransformer.php
new file mode 100644
index 0000000000..18d0f0f258
--- /dev/null
+++ b/src/Form/DataTransformer/FilterDataTransformer.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Form\DataTransformer;
+
+use Sonata\AdminBundle\Filter\Model\FilterData;
+use Symfony\Component\Form\DataTransformerInterface;
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+
+final class FilterDataTransformer implements DataTransformerInterface
+{
+ public function reverseTransform($value): FilterData
+ {
+ if (!\is_array($value)) {
+ throw new UnexpectedTypeException($value, 'array');
+ }
+
+ return FilterData::fromArray($value);
+ }
+
+ /**
+ * @param FilterData|null $value
+ */
+ public function transform($value)
+ {
+ if (null === $value) {
+ return null;
+ }
+
+ $data = [
+ 'type' => $value->getType(),
+ ];
+
+ if ($value->hasValue()) {
+ $data['value'] = $value->getValue();
+ }
+
+ return $data;
+ }
+}
diff --git a/src/Form/Type/Filter/ChoiceType.php b/src/Form/Type/Filter/ChoiceType.php
index d14238a036..2351c2d02d 100644
--- a/src/Form/Type/Filter/ChoiceType.php
+++ b/src/Form/Type/Filter/ChoiceType.php
@@ -16,7 +16,6 @@
use Sonata\AdminBundle\Form\Type\Operator\ContainsOperatorType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType as FormChoiceType;
-use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
@@ -29,23 +28,16 @@ public function getBlockPrefix(): string
return 'sonata_type_filter_choice';
}
- /**
- * @param array $options
- */
- public function buildForm(FormBuilderInterface $builder, array $options): void
+ public function getParent(): string
{
- $builder
- ->add('type', $options['operator_type'], $options['operator_options'] + ['required' => false])
- ->add('value', $options['field_type'], $options['field_options'] + ['required' => false]);
+ return FilterDataType::class;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'field_type' => FormChoiceType::class,
- 'field_options' => [],
'operator_type' => ContainsOperatorType::class,
- 'operator_options' => [],
]);
}
}
diff --git a/src/Form/Type/Filter/DateRangeType.php b/src/Form/Type/Filter/DateRangeType.php
index e2c6f475a2..066cca45cb 100644
--- a/src/Form/Type/Filter/DateRangeType.php
+++ b/src/Form/Type/Filter/DateRangeType.php
@@ -17,7 +17,6 @@
use Sonata\Form\Type\DateRangeType as FormDateRangeType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
-use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
@@ -30,19 +29,15 @@ public function getBlockPrefix(): string
return 'sonata_type_filter_date_range';
}
- /**
- * @param array $options
- */
- public function buildForm(FormBuilderInterface $builder, array $options): void
+ public function getParent(): string
{
- $builder
- ->add('type', DateRangeOperatorType::class, ['required' => false])
- ->add('value', $options['field_type'], $options['field_options'] + ['required' => false]);
+ return FilterDataType::class;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
+ 'operator_type' => DateRangeOperatorType::class,
'field_type' => FormDateRangeType::class,
'field_options' => [
'field_options' => [
diff --git a/src/Form/Type/Filter/DateTimeRangeType.php b/src/Form/Type/Filter/DateTimeRangeType.php
index fef9c2b86a..19dfcee803 100644
--- a/src/Form/Type/Filter/DateTimeRangeType.php
+++ b/src/Form/Type/Filter/DateTimeRangeType.php
@@ -17,7 +17,6 @@
use Sonata\Form\Type\DateTimeRangeType as FormDateTimeRangeType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
-use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
@@ -30,19 +29,15 @@ public function getBlockPrefix(): string
return 'sonata_type_filter_datetime_range';
}
- /**
- * @param array $options
- */
- public function buildForm(FormBuilderInterface $builder, array $options): void
+ public function getParent(): string
{
- $builder
- ->add('type', DateRangeOperatorType::class, ['required' => false])
- ->add('value', $options['field_type'], $options['field_options'] + ['required' => false]);
+ return FilterDataType::class;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
+ 'operator_type' => DateRangeOperatorType::class,
'field_type' => FormDateTimeRangeType::class,
'field_options' => [
'field_options' => ['date_format' => DateTimeType::HTML5_FORMAT],
diff --git a/src/Form/Type/Filter/DateTimeType.php b/src/Form/Type/Filter/DateTimeType.php
index a71152fa08..b90a5d42ff 100644
--- a/src/Form/Type/Filter/DateTimeType.php
+++ b/src/Form/Type/Filter/DateTimeType.php
@@ -17,7 +17,6 @@
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType as FormDateTimeType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
-use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
@@ -30,19 +29,15 @@ public function getBlockPrefix(): string
return 'sonata_type_filter_datetime';
}
- /**
- * @param array $options
- */
- public function buildForm(FormBuilderInterface $builder, array $options): void
+ public function getParent(): string
{
- $builder
- ->add('type', DateOperatorType::class, ['required' => false])
- ->add('value', $options['field_type'], $options['field_options'] + ['required' => false]);
+ return FilterDataType::class;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
+ 'operator_type' => DateOperatorType::class,
'field_type' => FormDateTimeType::class,
'field_options' => ['date_format' => DateType::HTML5_FORMAT],
]);
diff --git a/src/Form/Type/Filter/DateType.php b/src/Form/Type/Filter/DateType.php
index 8b102b6420..8db939704a 100644
--- a/src/Form/Type/Filter/DateType.php
+++ b/src/Form/Type/Filter/DateType.php
@@ -16,7 +16,6 @@
use Sonata\AdminBundle\Form\Type\Operator\DateOperatorType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType as FormDateType;
-use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
@@ -29,19 +28,15 @@ public function getBlockPrefix(): string
return 'sonata_type_filter_date';
}
- /**
- * @param array $options
- */
- public function buildForm(FormBuilderInterface $builder, array $options): void
+ public function getParent(): string
{
- $builder
- ->add('type', DateOperatorType::class, ['required' => false])
- ->add('value', $options['field_type'], $options['field_options'] + ['required' => false]);
+ return FilterDataType::class;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
+ 'operator_type' => DateOperatorType::class,
'field_type' => FormDateType::class,
'field_options' => ['format' => FormDateType::HTML5_FORMAT],
]);
diff --git a/src/Form/Type/Filter/DefaultType.php b/src/Form/Type/Filter/DefaultType.php
index 9b5a9d6853..c7e73f5495 100644
--- a/src/Form/Type/Filter/DefaultType.php
+++ b/src/Form/Type/Filter/DefaultType.php
@@ -16,7 +16,6 @@
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
-use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
@@ -29,23 +28,16 @@ public function getBlockPrefix(): string
return 'sonata_type_filter_default';
}
- /**
- * @param array $options
- */
- public function buildForm(FormBuilderInterface $builder, array $options): void
+ public function getParent(): string
{
- $builder
- ->add('type', $options['operator_type'], $options['operator_options'] + ['required' => false])
- ->add('value', $options['field_type'], $options['field_options'] + ['required' => false]);
+ return FilterDataType::class;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'operator_type' => HiddenType::class,
- 'operator_options' => [],
'field_type' => TextType::class,
- 'field_options' => [],
]);
}
}
diff --git a/src/Form/Type/Filter/FilterDataType.php b/src/Form/Type/Filter/FilterDataType.php
new file mode 100644
index 0000000000..35c4e8c13d
--- /dev/null
+++ b/src/Form/Type/Filter/FilterDataType.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Form\Type\Filter;
+
+use Sonata\AdminBundle\Form\DataTransformer\FilterDataTransformer;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+final class FilterDataType extends AbstractType
+{
+ /**
+ * @param array $options
+ */
+ public function buildForm(FormBuilderInterface $builder, array $options): void
+ {
+ $builder
+ ->add('type', $options['operator_type'], $options['operator_options'] + ['required' => false])
+ ->add('value', $options['field_type'], $options['field_options'] + ['required' => false]);
+
+ $builder
+ ->addModelTransformer(new FilterDataTransformer());
+ }
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setDefaults([
+ 'operator_options' => [],
+ 'field_options' => [],
+ ]);
+ $resolver
+ ->setRequired(['operator_type', 'field_type'])
+ ->setAllowedTypes('operator_type', 'string')
+ ->setAllowedTypes('field_type', 'string')
+ ->setAllowedTypes('operator_options', 'array')
+ ->setAllowedTypes('field_options', 'array');
+ }
+}
diff --git a/src/Form/Type/Filter/NumberType.php b/src/Form/Type/Filter/NumberType.php
index 14439069a7..4b38f1f98f 100644
--- a/src/Form/Type/Filter/NumberType.php
+++ b/src/Form/Type/Filter/NumberType.php
@@ -16,7 +16,6 @@
use Sonata\AdminBundle\Form\Type\Operator\NumberOperatorType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\NumberType as FormNumberType;
-use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
@@ -29,19 +28,15 @@ public function getBlockPrefix(): string
return 'sonata_type_filter_number';
}
- /**
- * @param array $options
- */
- public function buildForm(FormBuilderInterface $builder, array $options): void
+ public function getParent(): string
{
- $builder
- ->add('type', NumberOperatorType::class, ['required' => false])
- ->add('value', $options['field_type'], $options['field_options'] + ['required' => false]);
+ return FilterDataType::class;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
+ 'operator_type' => NumberOperatorType::class,
'field_type' => FormNumberType::class,
'field_options' => [],
]);
diff --git a/tests/Datagrid/DatagridTest.php b/tests/Datagrid/DatagridTest.php
index 1f1b36f6d6..3d53814bf1 100644
--- a/tests/Datagrid/DatagridTest.php
+++ b/tests/Datagrid/DatagridTest.php
@@ -374,13 +374,13 @@ public function testApplyFilter(?string $type, ?string $value, int $applyCallNum
*/
public function applyFilterDataProvider(): iterable
{
- yield ['fakeType', 'fakeValue', 1];
- yield ['', 'fakeValue', 1];
+ yield ['3', 'fakeValue', 1];
yield [null, 'fakeValue', 1];
- yield ['fakeType', '', 1];
- yield ['fakeType', null, 1];
- yield ['', '', 0];
- yield ['', null, 0];
+ yield [null, 'fakeValue', 1];
+ yield ['3', '', 1];
+ yield ['3', null, 1];
+ yield [null, '', 0];
+ yield [null, null, 0];
yield [null, '', 0];
yield [null, null, 0];
}
diff --git a/tests/Filter/Model/FilterDataTest.php b/tests/Filter/Model/FilterDataTest.php
new file mode 100644
index 0000000000..3ec6e90518
--- /dev/null
+++ b/tests/Filter/Model/FilterDataTest.php
@@ -0,0 +1,121 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Tests\Filter\Model;
+
+use PHPUnit\Framework\TestCase;
+use Sonata\AdminBundle\Filter\Model\FilterData;
+
+final class FilterDataTest extends TestCase
+{
+ /**
+ * @dataProvider getInvalidTypes
+ *
+ * @param mixed $type
+ */
+ public function testTypeMustBeNumericOrNull($type): void
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage(sprintf(
+ 'The "type" parameter MUST be of type "integer" or "null", %s given.',
+ \is_object($type) ? 'instance of "'.\get_class($type).'"' : '"'.\gettype($type).'"'
+ ));
+
+ FilterData::fromArray(['type' => $type]);
+ }
+
+ /**
+ * @return iterable>
+ */
+ public function getInvalidTypes(): iterable
+ {
+ yield ['string'];
+ yield [new \stdClass()];
+ yield [[]];
+ yield [42.0];
+ }
+
+ public function testEmptyArray(): void
+ {
+ $filterData = FilterData::fromArray([]);
+ $this->assertFalse($filterData->hasValue());
+ $this->assertNull($filterData->getType());
+ }
+
+ public function testHasValue(): void
+ {
+ $this->assertFalse(FilterData::fromArray([])->hasValue());
+ $this->assertTrue(FilterData::fromArray(['value' => ''])->hasValue());
+ $this->assertTrue(FilterData::fromArray(['value' => null])->hasValue());
+ }
+
+ /**
+ * @dataProvider getTypes
+ *
+ * @param int|string|null $type
+ */
+ public function testGetType(?int $expected, $type): void
+ {
+ $this->assertSame($expected, FilterData::fromArray(['type' => $type])->getType());
+ }
+
+ /**
+ * @return iterable>
+ */
+ public function getTypes(): iterable
+ {
+ yield 'nullable' => [null, null];
+ yield 'int' => [3, 3];
+ yield 'numeric string' => [3, '3'];
+ }
+
+ /**
+ * @dataProvider getValues
+ *
+ * @param mixed $value
+ */
+ public function testGetValue($value): void
+ {
+ $this->assertSame($value, FilterData::fromArray(['value' => $value])->getValue());
+ }
+
+ /**
+ * @return iterable>
+ */
+ public function getValues(): iterable
+ {
+ yield [null];
+ yield [new \stdClass()];
+ yield [3];
+ yield ['3'];
+ }
+
+ public function testSetValue(): void
+ {
+ $filterData = FilterData::fromArray(['type' => 1, 'value' => 'value']);
+ $newFilterData = $filterData->changeValue('new_value');
+
+ $this->assertSame(1, $newFilterData->getType());
+ $this->assertSame('new_value', $newFilterData->getValue());
+ }
+
+ public function testGetValueThrowsExceptionIfValueNotPresent(): void
+ {
+ $filterData = FilterData::fromArray([]);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The FilterData object does not have a value.');
+
+ $filterData->getValue();
+ }
+}
diff --git a/tests/Fixtures/Filter/BarFilter.php b/tests/Fixtures/Filter/BarFilter.php
index 21c811b77d..e8a5da98ee 100644
--- a/tests/Fixtures/Filter/BarFilter.php
+++ b/tests/Fixtures/Filter/BarFilter.php
@@ -15,11 +15,12 @@
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Sonata\AdminBundle\Filter\Filter;
+use Sonata\AdminBundle\Filter\Model\FilterData;
use Sonata\AdminBundle\Form\Type\Filter\DefaultType;
final class BarFilter extends Filter
{
- public function apply(ProxyQueryInterface $query, array $filterData): void
+ public function apply(ProxyQueryInterface $query, FilterData $filterData): void
{
}
diff --git a/tests/Fixtures/Filter/FooFilter.php b/tests/Fixtures/Filter/FooFilter.php
index f8d3d95aa6..89a3d84963 100644
--- a/tests/Fixtures/Filter/FooFilter.php
+++ b/tests/Fixtures/Filter/FooFilter.php
@@ -15,10 +15,11 @@
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Sonata\AdminBundle\Filter\Filter;
+use Sonata\AdminBundle\Filter\Model\FilterData;
class FooFilter extends Filter
{
- public function apply(ProxyQueryInterface $query, array $filterData): void
+ public function apply(ProxyQueryInterface $query, FilterData $filterData): void
{
}
diff --git a/tests/Form/DataTransformer/FilterDataTransformerTest.php b/tests/Form/DataTransformer/FilterDataTransformerTest.php
new file mode 100644
index 0000000000..f5036730e9
--- /dev/null
+++ b/tests/Form/DataTransformer/FilterDataTransformerTest.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Tests\Form\DataTransformer;
+
+use PHPUnit\Framework\TestCase;
+use Sonata\AdminBundle\Filter\Model\FilterData;
+use Sonata\AdminBundle\Form\DataTransformer\FilterDataTransformer;
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+
+final class FilterDataTransformerTest extends TestCase
+{
+ public function testReverseTransformThrowsExceptionIfValueIsNotArray(): void
+ {
+ $transformer = new FilterDataTransformer();
+
+ $this->expectException(UnexpectedTypeException::class);
+
+ $transformer->reverseTransform(1);
+ }
+
+ /**
+ * @dataProvider getDataValues
+ * @phpstan-param array{type: int, value: mixed} $value
+ */
+ public function testReverseTransform(array $value): void
+ {
+ $transformer = new FilterDataTransformer();
+
+ $filterData = $transformer->reverseTransform($value);
+
+ $this->assertSame($value['type'], $filterData->getType());
+ $this->assertSame($value['value'], $filterData->getValue());
+ }
+
+ public function testTransformReturnsNullOnNull(): void
+ {
+ $transformer = new FilterDataTransformer();
+
+ $this->assertNull($transformer->transform(null));
+ }
+
+ /**
+ * @dataProvider getDataValues
+ * @phpstan-param array{type: int, value: mixed} $value
+ */
+ public function testTransform(array $value): void
+ {
+ $transformer = new FilterDataTransformer();
+
+ $this->assertSame($value, $transformer->transform(FilterData::fromArray($value)));
+ }
+
+ /**
+ * @phpstan-return iterable>
+ */
+ public function getDataValues(): iterable
+ {
+ yield [[
+ 'type' => 1,
+ 'value' => 'value',
+ ]];
+
+ yield [[
+ 'type' => 1,
+ 'value' => null,
+ ]];
+ }
+}
diff --git a/tests/Form/Type/Filter/DateTimeRangeTypeTest.php b/tests/Form/Type/Filter/DateTimeRangeTypeTest.php
index ef9b45650e..f62df5a59a 100644
--- a/tests/Form/Type/Filter/DateTimeRangeTypeTest.php
+++ b/tests/Form/Type/Filter/DateTimeRangeTypeTest.php
@@ -14,6 +14,7 @@
namespace Sonata\AdminBundle\Tests\Form\Type\Filter;
use Sonata\AdminBundle\Form\Type\Filter\DateTimeRangeType;
+use Sonata\AdminBundle\Form\Type\Operator\DateRangeOperatorType;
use Sonata\Form\Type\DateTimeRangeType as FormDateTimeRangeType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -41,6 +42,7 @@ public function testGetDefaultOptions(): void
$options = $optionsResolver->resolve();
$expected = [
+ 'operator_type' => DateRangeOperatorType::class,
'field_type' => FormDateTimeRangeType::class,
'field_options' => ['field_options' => ['date_format' => DateTimeType::HTML5_FORMAT]],
];