From d5f64b1bf4b3f0827ae9b596c8a69467aa4604d7 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Mon, 26 Apr 2021 19:48:42 +0200 Subject: [PATCH] Add FilterData Adding this VO introduced in next major version allows us to add forward compatibility for callback filter functions. --- src/Filter/Model/FilterData.php | 106 ++++++++++++++++++++++ tests/Filter/Model/FilterDataTest.php | 121 ++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 src/Filter/Model/FilterData.php create mode 100644 tests/Filter/Model/FilterDataTest.php 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/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(); + } +}