Skip to content

Commit 7155513

Browse files
Add like handler factory (#31)
* Fix not executing tests, rename * Apply fixes from StyleCI * Fix * WIP * Fix Psalm * Fix * Sync * Apply fixes from StyleCI * WIP * Apply fixes from StyleCI * Fix * Apply fixes from StyleCI * WIP * Adjust composer.json * Adjust composer.json * Fix * Fix * Fix * Fix 2 * Fix 3 * Fix * Fix * Apply fixes from StyleCI * Fix * Apply fixes from StyleCI * WIP * WIP * WIP * Apply fixes from StyleCI * WIP * WIP * WIP * Fix * Fix * Fix * Fix * Use dev-master for data * Trigger CI * An attempt to fix mssql tests in CI --------- Co-authored-by: StyleCI Bot <[email protected]>
1 parent de805e5 commit 7155513

20 files changed

+312
-33
lines changed

.github/workflows/mssql.yml

+6-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
name: PHP ${{ matrix.php }}-mssql-${{ matrix.mssql.server }}
2424

2525
env:
26-
extensions: pdo, pdo_sqlsrv-5.11.1
26+
extensions: pdo, pdo_sqlsrv-5.12
2727

2828
runs-on: ${{ matrix.mssql.os || 'ubuntu-latest' }}
2929

@@ -62,6 +62,11 @@ jobs:
6262
options: --name=mssql --health-cmd="/opt/mssql-tools${{ matrix.mssql.odbc-version }}/bin/sqlcmd ${{ matrix.mssql.flag }} -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'" --health-interval=10s --health-timeout=5s --health-retries=3
6363

6464
steps:
65+
- name: Install ODBC driver.
66+
run: |
67+
sudo curl https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
68+
sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18
69+
6570
- name: Checkout
6671
uses: actions/checkout@v3
6772

composer.json

+7-6
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,18 @@
3333
"minimum-stability": "dev",
3434
"require": {
3535
"php": "^8.1",
36-
"cycle/database": "^2.10",
37-
"cycle/orm": "^2.7.1",
36+
"ext-mbstring": "*",
37+
"cycle/database": "^2.11",
38+
"cycle/orm": "^2.9",
3839
"yiisoft/data": "dev-master"
3940
},
4041
"require-dev": {
4142
"maglnet/composer-require-checker": "^4.7",
4243
"phpunit/phpunit": "^10.5",
43-
"rector/rector": "^1.0",
44-
"roave/infection-static-analysis-plugin": "^1.34",
45-
"spatie/phpunit-watcher": "^1.23",
46-
"vimeo/psalm": "^5.21",
44+
"rector/rector": "^1.2",
45+
"roave/infection-static-analysis-plugin": "^1.35",
46+
"spatie/phpunit-watcher": "^1.24",
47+
"vimeo/psalm": "^5.26",
4748
"vlucas/phpdotenv": "^5.6"
4849
},
4950
"autoload": {

phpunit.xml.dist

+6-4
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,24 @@
2121
<testsuite name="Sqlite">
2222
<directory>./tests/Feature/Sqlite</directory>
2323
<directory>./tests/Exception</directory>
24-
<directory>./tests/Unit</directory>
24+
<directory>./tests/Unit/Sqlite</directory>
25+
<directory>./tests/Unit/Reader</directory>
2526
</testsuite>
2627
<testsuite name="Mysql">
2728
<directory>./tests/Feature/Mysql</directory>
2829
<directory>./tests/Exception</directory>
29-
<directory>./tests/Unit</directory>
30+
<directory>./tests/Unit/Reader</directory>
3031
</testsuite>
3132
<testsuite name="Pgsql">
3233
<directory>./tests/Feature/Pgsql</directory>
3334
<directory>./tests/Exception</directory>
34-
<directory>./tests/Unit</directory>
35+
<directory>./tests/Unit/Reader</directory>
3536
</testsuite>
3637
<testsuite name="Mssql">
3738
<directory>./tests/Feature/Mssql</directory>
3839
<directory>./tests/Exception</directory>
39-
<directory>./tests/Unit</directory>
40+
<directory>./tests/Unit/Mssql</directory>
41+
<directory>./tests/Unit/Reader</directory>
4042
</testsuite>
4143
</testsuites>
4244

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Data\Cycle\Exception;
6+
7+
use InvalidArgumentException;
8+
use Throwable;
9+
10+
final class NotSupportedFilterOptionException extends InvalidArgumentException
11+
{
12+
/**
13+
* @param string $optionName Option name in filter.
14+
* @param string $driverType Driver type of database.
15+
*/
16+
public function __construct(string $optionName, string $driverType, int $code = 0, ?Throwable $previous = null)
17+
{
18+
parent::__construct("\$$optionName option is not supported when using $driverType driver.", $code, $previous);
19+
}
20+
}

src/Exception/UnexpectedFilterException.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ final class UnexpectedFilterException extends InvalidArgumentException
1111
{
1212
/**
1313
* @param string $expectedClassName Expected class name of a filter.
14-
* @psalm-param class-string $actualClassName
14+
* @psalm-param class-string $expectedClassName
1515
*
1616
* @param string $actualClassName An actual given filter that's not an instance of a specific filter with `$expectedClassName`.
17-
* @psalm-param class-string $ac
17+
* @psalm-param class-string $actualClassName
1818
*/
1919
public function __construct(
2020
string $expectedClassName,

src/Reader/EntityReader.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Generator;
1212
use InvalidArgumentException;
1313
use Yiisoft\Data\Cycle\Exception\NotSupportedFilterException;
14+
use Yiisoft\Data\Cycle\Reader\FilterHandler\LikeHandler\LikeHandlerFactory;
1415
use Yiisoft\Data\Reader\DataReaderInterface;
1516
use Yiisoft\Data\Reader\FilterHandlerInterface;
1617
use Yiisoft\Data\Reader\FilterInterface;
@@ -48,6 +49,11 @@ public function __construct(Select|SelectQuery $query)
4849
$this->countCache = new CachedCount($this->query);
4950
$this->itemsCache = new CachedCollection();
5051
$this->oneItemCache = new CachedCollection();
52+
/**
53+
* @psalm-suppress InternalMethod There is no other way to get driver for SelectQuery.
54+
* @psalm-suppress UndefinedMagicMethod The magic method is not defined in annotations.
55+
*/
56+
$likeHandler = LikeHandlerFactory::getLikeHandler($this->query->getDriver());
5157
$this->setFilterHandlers(
5258
new FilterHandler\AllHandler(),
5359
new FilterHandler\AnyHandler(),
@@ -59,7 +65,7 @@ public function __construct(Select|SelectQuery $query)
5965
new FilterHandler\InHandler(),
6066
new FilterHandler\LessThanHandler(),
6167
new FilterHandler\LessThanOrEqualHandler(),
62-
new FilterHandler\LikeHandler(),
68+
$likeHandler,
6369
new FilterHandler\NotHandler(),
6470
);
6571
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Data\Cycle\Reader\FilterHandler\LikeHandler;
6+
7+
use Yiisoft\Data\Reader\Filter\Like;
8+
use Yiisoft\Data\Reader\FilterHandlerInterface;
9+
10+
abstract class BaseLikeHandler implements FilterHandlerInterface
11+
{
12+
protected array $escapingReplacements = [
13+
'%' => '\%',
14+
'_' => '\_',
15+
'\\' => '\\\\',
16+
];
17+
18+
public function getFilterClass(): string
19+
{
20+
return Like::class;
21+
}
22+
23+
protected function prepareValue(string $value): string
24+
{
25+
return '%' . strtr($value, $this->escapingReplacements) . '%';
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Data\Cycle\Reader\FilterHandler\LikeHandler;
6+
7+
use Cycle\Database\Driver\DriverInterface;
8+
use RuntimeException;
9+
use Yiisoft\Data\Reader\FilterHandlerInterface;
10+
11+
class LikeHandlerFactory
12+
{
13+
public static function getLikeHandler(?DriverInterface $databaseDriver): FilterHandlerInterface
14+
{
15+
$driverType = $databaseDriver?->getType() ?? 'SQLite';
16+
17+
// default - ignored due to the complexity of testing and preventing splitting of databaseDriver argument.
18+
// @codeCoverageIgnoreStart
19+
return match ($driverType) {
20+
'SQLite' => new SqliteLikeHandler(),
21+
'MySQL' => new MysqlLikeHandler(),
22+
'Postgres' => new PostgresLikeHandler(),
23+
'SQLServer' => new SqlServerLikeHandler(),
24+
default => throw new RuntimeException("$driverType database driver is not supported."),
25+
};
26+
// @codeCoverageIgnoreEnd
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Data\Cycle\Reader\FilterHandler\LikeHandler;
6+
7+
use Yiisoft\Data\Cycle\Exception\UnexpectedFilterException;
8+
use Yiisoft\Data\Cycle\Reader\QueryBuilderFilterHandler;
9+
use Yiisoft\Data\Reader\Filter\Like;
10+
use Yiisoft\Data\Reader\FilterInterface;
11+
12+
final class MysqlLikeHandler extends BaseLikeHandler implements QueryBuilderFilterHandler
13+
{
14+
public function getAsWhereArguments(FilterInterface $filter, array $handlers): array
15+
{
16+
if (!$filter instanceof Like) {
17+
throw new UnexpectedFilterException(Like::class, $filter::class);
18+
}
19+
20+
if ($filter->isCaseSensitive() !== true) {
21+
return [$filter->getField(), 'like', '%' . $this->prepareValue($filter->getValue()) . '%'];
22+
}
23+
24+
return [$filter->getField(), 'like binary', $this->prepareValue($filter->getValue())];
25+
}
26+
}

src/Reader/FilterHandler/LikeHandler.php src/Reader/FilterHandler/LikeHandler/PostgresLikeHandler.php

+8-10
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,25 @@
22

33
declare(strict_types=1);
44

5-
namespace Yiisoft\Data\Cycle\Reader\FilterHandler;
5+
namespace Yiisoft\Data\Cycle\Reader\FilterHandler\LikeHandler;
66

77
use Yiisoft\Data\Cycle\Exception\UnexpectedFilterException;
8-
use Yiisoft\Data\Reader\Filter\Like;
9-
use Yiisoft\Data\Reader\FilterHandlerInterface;
108
use Yiisoft\Data\Cycle\Reader\QueryBuilderFilterHandler;
9+
use Yiisoft\Data\Reader\Filter\Like;
1110
use Yiisoft\Data\Reader\FilterInterface;
1211

13-
final class LikeHandler implements QueryBuilderFilterHandler, FilterHandlerInterface
12+
final class PostgresLikeHandler extends BaseLikeHandler implements QueryBuilderFilterHandler
1413
{
15-
public function getFilterClass(): string
16-
{
17-
return Like::class;
18-
}
19-
2014
public function getAsWhereArguments(FilterInterface $filter, array $handlers): array
2115
{
2216
if (!$filter instanceof Like) {
2317
throw new UnexpectedFilterException(Like::class, $filter::class);
2418
}
2519

26-
return [$filter->getField(), 'like', '%' . $filter->getValue() . '%'];
20+
if ($filter->isCaseSensitive() !== true) {
21+
return [$filter->getField(), 'ilike', $this->prepareValue($filter->getValue())];
22+
}
23+
24+
return [$filter->getField(), 'like', $this->prepareValue($filter->getValue())];
2725
}
2826
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Data\Cycle\Reader\FilterHandler\LikeHandler;
6+
7+
use Yiisoft\Data\Cycle\Exception\NotSupportedFilterOptionException;
8+
use Yiisoft\Data\Cycle\Exception\UnexpectedFilterException;
9+
use Yiisoft\Data\Cycle\Reader\QueryBuilderFilterHandler;
10+
use Yiisoft\Data\Reader\Filter\Like;
11+
use Yiisoft\Data\Reader\FilterInterface;
12+
13+
final class SqlServerLikeHandler extends BaseLikeHandler implements QueryBuilderFilterHandler
14+
{
15+
public function __construct()
16+
{
17+
unset($this->escapingReplacements['\\']);
18+
}
19+
20+
public function getAsWhereArguments(FilterInterface $filter, array $handlers): array
21+
{
22+
if (!$filter instanceof Like) {
23+
throw new UnexpectedFilterException(Like::class, $filter::class);
24+
}
25+
26+
if ($filter->isCaseSensitive() === true) {
27+
throw new NotSupportedFilterOptionException(optionName: 'caseSensitive', driverType: 'SQLServer');
28+
}
29+
30+
return [$filter->getField(), 'like', $this->prepareValue($filter->getValue())];
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Data\Cycle\Reader\FilterHandler\LikeHandler;
6+
7+
use Yiisoft\Data\Cycle\Exception\NotSupportedFilterOptionException;
8+
use Yiisoft\Data\Cycle\Exception\UnexpectedFilterException;
9+
use Yiisoft\Data\Cycle\Reader\QueryBuilderFilterHandler;
10+
use Yiisoft\Data\Reader\Filter\Like;
11+
use Yiisoft\Data\Reader\FilterInterface;
12+
13+
final class SqliteLikeHandler extends BaseLikeHandler implements QueryBuilderFilterHandler
14+
{
15+
public function __construct()
16+
{
17+
unset($this->escapingReplacements['\\']);
18+
}
19+
20+
public function getAsWhereArguments(FilterInterface $filter, array $handlers): array
21+
{
22+
if (!$filter instanceof Like) {
23+
throw new UnexpectedFilterException(Like::class, $filter::class);
24+
}
25+
26+
if ($filter->isCaseSensitive() === true) {
27+
throw new NotSupportedFilterOptionException(optionName: 'caseSensitive', driverType: 'SQLite');
28+
}
29+
30+
return [$filter->getField(), 'like', $this->prepareValue($filter->getValue())];
31+
}
32+
}

src/Reader/QueryBuilderFilterHandler.php

+3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44

55
namespace Yiisoft\Data\Cycle\Reader;
66

7+
use Yiisoft\Data\Cycle\Exception\UnexpectedFilterException;
78
use Yiisoft\Data\Reader\FilterHandlerInterface;
89
use Yiisoft\Data\Reader\FilterInterface;
910

1011
interface QueryBuilderFilterHandler
1112
{
1213
/**
1314
* @psalm-param array<class-string, FilterHandlerInterface & QueryBuilderFilterHandler> $handlers
15+
*
16+
* @throws UnexpectedFilterException When filter does not match the expected one.
1417
*/
1518
public function getAsWhereArguments(FilterInterface $filter, array $handlers): array;
1619
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Data\Cycle\Tests\Exception;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Yiisoft\Data\Cycle\Exception\NotSupportedFilterOptionException;
9+
10+
final class NotSupportedFilterOptionExceptionTest extends TestCase
11+
{
12+
public function testBase(): void
13+
{
14+
$exception = new NotSupportedFilterOptionException(optionName: 'caseSensitive', driverType: 'SQLite');
15+
16+
$this->assertSame('$caseSensitive option is not supported when using SQLite driver.', $exception->getMessage());
17+
$this->assertSame(0, $exception->getCode());
18+
$this->assertNull($exception->getPrevious());
19+
}
20+
}

tests/Feature/Base/Reader/ReaderWithFilter/BaseReaderWithLikeTestCase.php

+1-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ abstract class BaseReaderWithLikeTestCase extends \Yiisoft\Data\Tests\Common\Rea
1313
public static function dataWithReader(): array
1414
{
1515
$data = parent::dataWithReader();
16-
unset(
17-
$data['search: contains, different case, case sensitive: false'],
18-
$data['search: contains, different case, case sensitive: true'],
19-
$data['wildcard is not supported'],
20-
);
16+
$data['search: contains, different case, case sensitive: null'] = ['email', 'SEED@', null, [2]];
2117

2218
return $data;
2319
}

tests/Feature/Mysql/Reader/ReaderWithFilter/ReaderWithLikeTest.php

+9
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,13 @@
99
final class ReaderWithLikeTest extends BaseReaderWithLikeTestCase
1010
{
1111
public static $DRIVER = 'mysql';
12+
13+
public static function dataWithReader(): array
14+
{
15+
$data = parent::dataWithReader();
16+
$data['search: contains, same case, case sensitive: true'] = ['email', 'ed@be', true, [2]];
17+
$data['search: contains, different case, case sensitive: true'] = ['email', 'SEED@', true, []];
18+
19+
return $data;
20+
}
1221
}

0 commit comments

Comments
 (0)