diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ab028..9913505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 2.0.0 under development +- New #237: Add `Stringable` type support to filter values (@vjik) - New #150: Extract `withLimit()` from `ReadableDataInterface` into `LimitableDataInterface` (@vjik) - Enh #150: `PaginatorInterface` now extends `ReadableDataInterface` (@vjik) - Chg #151: Rename `isRequired()` method in `PaginatorInterface` to `isPaginationRequired()` (@vjik) diff --git a/src/Reader/Filter/Between.php b/src/Reader/Filter/Between.php index dad52c3..092f5f4 100644 --- a/src/Reader/Filter/Between.php +++ b/src/Reader/Filter/Between.php @@ -5,6 +5,7 @@ namespace Yiisoft\Data\Reader\Filter; use DateTimeInterface; +use Stringable; use Yiisoft\Data\Reader\FilterInterface; /** @@ -15,13 +16,13 @@ final class Between implements FilterInterface { /** * @param string $field Name of the field to compare. - * @param bool|DateTimeInterface|float|int|string $minValue Minimal field value. - * @param bool|DateTimeInterface|float|int|string $maxValue Maximal field value. + * @param bool|DateTimeInterface|float|int|string|Stringable $minValue Minimal field value. + * @param bool|DateTimeInterface|float|int|string|Stringable $maxValue Maximal field value. */ public function __construct( public readonly string $field, - public readonly bool|DateTimeInterface|float|int|string $minValue, - public readonly bool|DateTimeInterface|float|int|string $maxValue + public readonly bool|DateTimeInterface|float|int|string|Stringable $minValue, + public readonly bool|DateTimeInterface|float|int|string|Stringable $maxValue ) { } } diff --git a/src/Reader/Filter/Compare.php b/src/Reader/Filter/Compare.php index 87d8551..afefa1a 100644 --- a/src/Reader/Filter/Compare.php +++ b/src/Reader/Filter/Compare.php @@ -5,6 +5,7 @@ namespace Yiisoft\Data\Reader\Filter; use DateTimeInterface; +use Stringable; use Yiisoft\Data\Reader\FilterInterface; /** @@ -14,18 +15,18 @@ abstract class Compare implements FilterInterface { /** * @param string $field Name of the field to compare. - * @param bool|DateTimeInterface|float|int|string $value Value to compare to. + * @param bool|DateTimeInterface|float|int|string|Stringable $value Value to compare to. */ final public function __construct( public readonly string $field, - public readonly bool|DateTimeInterface|float|int|string $value, + public readonly bool|DateTimeInterface|float|int|string|Stringable $value, ) { } /** - * @param bool|DateTimeInterface|float|int|string $value Value to compare to. + * @param bool|DateTimeInterface|float|int|string|Stringable $value Value to compare to. */ - final public function withValue(bool|DateTimeInterface|float|int|string $value): static + final public function withValue(bool|DateTimeInterface|float|int|string|Stringable $value): static { return new static($this->field, $value); } diff --git a/src/Reader/Filter/In.php b/src/Reader/Filter/In.php index fb818ae..911df61 100644 --- a/src/Reader/Filter/In.php +++ b/src/Reader/Filter/In.php @@ -5,6 +5,7 @@ namespace Yiisoft\Data\Reader\Filter; use InvalidArgumentException; +use Stringable; use Yiisoft\Data\Reader\FilterInterface; use function is_scalar; @@ -17,11 +18,11 @@ final class In implements FilterInterface { /** * @param string $field Name of the field to compare. - * @param bool[]|float[]|int[]|string[] $values Values to check against. + * @param bool[]|float[]|int[]|string[]|Stringable[] $values Values to check against. */ public function __construct( public readonly string $field, - /** @var bool[]|float[]|int[]|string[] Values to check against. */ + /** @var bool[]|float[]|int[]|string[]|Stringable[] Values to check against. */ public readonly array $values ) { $this->assertValues($values); @@ -30,10 +31,10 @@ public function __construct( private function assertValues(array $values): void { foreach ($values as $value) { - if (!is_scalar($value)) { + if (!is_scalar($value) && !$value instanceof Stringable) { throw new InvalidArgumentException( sprintf( - 'The value should be scalar. "%s" is received.', + 'The value should be scalar or Stringable. "%s" is received.', get_debug_type($value), ) ); diff --git a/src/Reader/Filter/Like.php b/src/Reader/Filter/Like.php index 91b3bc3..6b12482 100644 --- a/src/Reader/Filter/Like.php +++ b/src/Reader/Filter/Like.php @@ -4,6 +4,7 @@ namespace Yiisoft\Data\Reader\Filter; +use Stringable; use Yiisoft\Data\Reader\FilterInterface; /** @@ -13,7 +14,7 @@ final class Like implements FilterInterface { /** * @param string $field Name of the field to compare. - * @param string $value Value to like-compare with. + * @param string|Stringable $value Value to like-compare with. * @param bool|null $caseSensitive Whether search must be case-sensitive: * * - `null` - depends on implementation; @@ -23,7 +24,7 @@ final class Like implements FilterInterface */ public function __construct( public readonly string $field, - public readonly string $value, + public readonly string|Stringable $value, public readonly ?bool $caseSensitive = null, public readonly LikeMode $mode = LikeMode::Contains, ) { diff --git a/src/Reader/Iterable/FilterHandler/LikeHandler.php b/src/Reader/Iterable/FilterHandler/LikeHandler.php index 204b75c..d70e009 100644 --- a/src/Reader/Iterable/FilterHandler/LikeHandler.php +++ b/src/Reader/Iterable/FilterHandler/LikeHandler.php @@ -31,14 +31,15 @@ public function match(object|array $item, FilterInterface $filter, Context $cont return false; } - if ($filter->value === '') { + $searchValue = (string) $filter->value; + if ($searchValue === '') { return true; } return match ($filter->mode) { - LikeMode::Contains => $this->matchContains($itemValue, $filter->value, $filter->caseSensitive), - LikeMode::StartsWith => $this->matchStartsWith($itemValue, $filter->value, $filter->caseSensitive), - LikeMode::EndsWith => $this->matchEndsWith($itemValue, $filter->value, $filter->caseSensitive), + LikeMode::Contains => $this->matchContains($itemValue, $searchValue, $filter->caseSensitive), + LikeMode::StartsWith => $this->matchStartsWith($itemValue, $searchValue, $filter->caseSensitive), + LikeMode::EndsWith => $this->matchEndsWith($itemValue, $searchValue, $filter->caseSensitive), }; } diff --git a/tests/Common/Reader/ReaderWithFilter/BaseReaderWithBetweenTestCase.php b/tests/Common/Reader/ReaderWithFilter/BaseReaderWithBetweenTestCase.php index 1d50992..edc480d 100644 --- a/tests/Common/Reader/ReaderWithFilter/BaseReaderWithBetweenTestCase.php +++ b/tests/Common/Reader/ReaderWithFilter/BaseReaderWithBetweenTestCase.php @@ -8,11 +8,13 @@ use PHPUnit\Framework\Attributes\DataProvider; use Yiisoft\Data\Reader\Filter\Between; use Yiisoft\Data\Tests\Common\Reader\BaseReaderTestCase; +use Yiisoft\Data\Tests\Support\StringableValue; abstract class BaseReaderWithBetweenTestCase extends BaseReaderTestCase { public static function dataWithReader(): iterable { + yield 'stringable' => [new Between('email', new StringableValue('ta'), new StringableValue('tz')), [4, 5]]; yield 'float' => [new Between('balance', 10.25, 100.0), [1, 3, 5]]; yield 'datetime' => [new Between('born_at', new DateTimeImmutable('1989-01-01'), new DateTimeImmutable('1991-01-01')), [5]]; yield 'datetime 2' => [new Between('born_at', new DateTimeImmutable('1990-01-02'), new DateTimeImmutable('1990-01-03')), []]; diff --git a/tests/Common/Reader/ReaderWithFilter/BaseReaderWithEqualsTestCase.php b/tests/Common/Reader/ReaderWithFilter/BaseReaderWithEqualsTestCase.php index 75dc26c..545cfc3 100644 --- a/tests/Common/Reader/ReaderWithFilter/BaseReaderWithEqualsTestCase.php +++ b/tests/Common/Reader/ReaderWithFilter/BaseReaderWithEqualsTestCase.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\Attributes\DataProvider; use Yiisoft\Data\Reader\Filter\Equals; use Yiisoft\Data\Tests\Common\Reader\BaseReaderTestCase; +use Yiisoft\Data\Tests\Support\StringableValue; abstract class BaseReaderWithEqualsTestCase extends BaseReaderTestCase { @@ -16,6 +17,7 @@ public static function dataWithReader(): iterable yield 'integer' => [new Equals('number', 2), [2]]; yield 'float' => [new Equals('balance', 10.25), [1]]; yield 'string' => [new Equals('email', 'the@best'), [4]]; + yield 'stringable' => [new Equals('email', new StringableValue('the@best')), [4]]; yield 'datetime' => [new Equals('born_at', new DateTimeImmutable('1990-01-01')), [5]]; } diff --git a/tests/Common/Reader/ReaderWithFilter/BaseReaderWithInTestCase.php b/tests/Common/Reader/ReaderWithFilter/BaseReaderWithInTestCase.php index 4bef8ce..290387b 100644 --- a/tests/Common/Reader/ReaderWithFilter/BaseReaderWithInTestCase.php +++ b/tests/Common/Reader/ReaderWithFilter/BaseReaderWithInTestCase.php @@ -4,14 +4,26 @@ namespace Yiisoft\Data\Tests\Common\Reader\ReaderWithFilter; +use PHPUnit\Framework\Attributes\DataProvider; use Yiisoft\Data\Reader\Filter\In; use Yiisoft\Data\Tests\Common\Reader\BaseReaderTestCase; +use Yiisoft\Data\Tests\Support\StringableValue; abstract class BaseReaderWithInTestCase extends BaseReaderTestCase { - public function testWithReader(): void + public static function dataWithReader(): iterable { - $reader = $this->getReader()->withFilter(new In('number', [2, 3])); - $this->assertFixtures([1, 2], $reader->read()); + yield 'int' => [new In('number', [2, 3]), [1, 2]]; + yield 'stringable' => [ + new In('email', [new StringableValue('seed@beat'), new StringableValue('the@best')]), + [2, 3], + ]; + } + + #[DataProvider('dataWithReader')] + public function testWithReader(In $filter, array $expected): void + { + $reader = $this->getReader()->withFilter($filter); + $this->assertFixtures($expected, $reader->read()); } } diff --git a/tests/Common/Reader/ReaderWithFilter/BaseReaderWithLikeTestCase.php b/tests/Common/Reader/ReaderWithFilter/BaseReaderWithLikeTestCase.php index 6bc7638..8d1a3e4 100644 --- a/tests/Common/Reader/ReaderWithFilter/BaseReaderWithLikeTestCase.php +++ b/tests/Common/Reader/ReaderWithFilter/BaseReaderWithLikeTestCase.php @@ -8,6 +8,7 @@ use Yiisoft\Data\Reader\Filter\Like; use Yiisoft\Data\Reader\Filter\LikeMode; use Yiisoft\Data\Tests\Common\Reader\BaseReaderTestCase; +use Yiisoft\Data\Tests\Support\StringableValue; abstract class BaseReaderWithLikeTestCase extends BaseReaderTestCase { @@ -24,13 +25,14 @@ public static function dataWithReader(): array 'wildcard is not supported, %' => ['email', '%st', null, []], 'wildcard is not supported, _' => ['email', '____@___t', null, []], 'search: contains backslash' => ['email', 'foo@bar\\baz', null, [0]], + 'stringable' => ['email', new StringableValue('seed@'), null, [2]], ]; } #[DataProvider('dataWithReader')] public function testWithReader( string $field, - string $value, + mixed $value, bool|null $caseSensitive, array $expectedFixtureIndexes, ): void { diff --git a/tests/Reader/Filter/InTest.php b/tests/Reader/Filter/InTest.php index 555e4ce..8dc4ec3 100644 --- a/tests/Reader/Filter/InTest.php +++ b/tests/Reader/Filter/InTest.php @@ -14,7 +14,7 @@ final class InTest extends TestCase public function testNotScalarValues(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The value should be scalar. "' . stdClass::class . '" is received.'); + $this->expectExceptionMessage('The value should be scalar or Stringable. "' . stdClass::class . '" is received.'); new In('test', [new stdClass()]); } } diff --git a/tests/Support/StringableValue.php b/tests/Support/StringableValue.php new file mode 100644 index 0000000..2d61b5f --- /dev/null +++ b/tests/Support/StringableValue.php @@ -0,0 +1,20 @@ +value; + } +}