From 143b632b47a2cea17562a8e61d8dd11c54259e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 21 Aug 2024 17:25:56 +0200 Subject: [PATCH 1/2] PHPORM-232 Support whereLike and whereNotLike --- CHANGELOG.md | 1 + src/Query/Builder.php | 10 +++++++++- tests/Query/BuilderTest.php | 30 ++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6e7c9144..ac591881f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## [4.8.0] - next * Add `Query\Builder::incrementEach()` and `decrementEach()` methods by @SmallRuralDog in [#2550](https://github.com/mongodb/laravel-mongodb/pull/2550) +* Add `Query\Builder::whereLike()` and `whereNotLike()` methods by @GromNaN in [#3108](https://github.com/mongodb/laravel-mongodb/pull/3108) * Deprecate `Connection::collection()` and `Schema\Builder::collection()` methods by @GromNaN in [#3062](https://github.com/mongodb/laravel-mongodb/pull/3062) * Deprecate `Model::$collection` property to customize collection name. Use `$table` instead by @GromNaN in [#3064](https://github.com/mongodb/laravel-mongodb/pull/3064) diff --git a/src/Query/Builder.php b/src/Query/Builder.php index ddc2413d8..8c7a37854 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -1266,7 +1266,8 @@ protected function compileWhereBasic(array $where): array // All backslashes are converted to \\, which are needed in matching regexes. preg_quote($value), ); - $value = new Regex('^' . $regex . '$', 'i'); + $flags = $where['caseSensitive'] ?? false ? '' : 'i'; + $value = new Regex('^' . $regex . '$', $flags); // For inverse like operations, we can just use the $not operator with the Regex $operator = $operator === 'like' ? '=' : 'not'; @@ -1324,6 +1325,13 @@ protected function compileWhereNotIn(array $where): array return [$where['column'] => ['$nin' => array_values($where['values'])]]; } + protected function compileWhereLike(array $where): array + { + $where['operator'] = $where['not'] ? 'not like' : 'like'; + + return $this->compileWhereBasic($where); + } + protected function compileWhereNull(array $where): array { $where['operator'] = '='; diff --git a/tests/Query/BuilderTest.php b/tests/Query/BuilderTest.php index 3ec933499..6901a5a30 100644 --- a/tests/Query/BuilderTest.php +++ b/tests/Query/BuilderTest.php @@ -748,6 +748,36 @@ function (Builder $builder) { fn (Builder $builder) => $builder->where('name', 'like', '_ac__me_'), ]; + yield 'whereLike' => [ + ['find' => [['name' => new Regex('^1$', 'i')], []]], + fn (Builder $builder) => $builder->whereLike('name', '1'), + ]; + + yield 'whereLike case not sensitive' => [ + ['find' => [['name' => new Regex('^1$', 'i')], []]], + fn (Builder $builder) => $builder->whereLike('name', '1', false), + ]; + + yield 'whereLike case sensitive' => [ + ['find' => [['name' => new Regex('^1$', '')], []]], + fn (Builder $builder) => $builder->whereLike('name', '1', true), + ]; + + yield 'whereNotLike' => [ + ['find' => [['name' => ['$not' => new Regex('^1$', 'i')]], []]], + fn (Builder $builder) => $builder->whereNotLike('name', '1'), + ]; + + yield 'whereNotLike case not sensitive' => [ + ['find' => [['name' => ['$not' => new Regex('^1$', 'i')]], []]], + fn (Builder $builder) => $builder->whereNotLike('name', '1', false), + ]; + + yield 'whereNotLike case sensitive' => [ + ['find' => [['name' => ['$not' => new Regex('^1$', '')]], []]], + fn (Builder $builder) => $builder->whereNotLike('name', '1', true), + ]; + $regex = new Regex('^acme$', 'si'); yield 'where BSON\Regex' => [ ['find' => [['name' => $regex], []]], From 95455d28c38cc3b1ad47b0c033f4978c083ce92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 22 Aug 2024 08:54:37 +0200 Subject: [PATCH 2/2] Check required methods from Laravel in tests --- tests/Query/BuilderTest.php | 263 ++++++++++++++++++++---------------- 1 file changed, 146 insertions(+), 117 deletions(-) diff --git a/tests/Query/BuilderTest.php b/tests/Query/BuilderTest.php index 6901a5a30..c5c13260b 100644 --- a/tests/Query/BuilderTest.php +++ b/tests/Query/BuilderTest.php @@ -26,13 +26,18 @@ use function collect; use function method_exists; use function now; +use function sprintf; use function var_export; class BuilderTest extends TestCase { #[DataProvider('provideQueryBuilderToMql')] - public function testMql(array $expected, Closure $build): void + public function testMql(array $expected, Closure $build, ?string $requiredMethod = null): void { + if ($requiredMethod && ! method_exists(Builder::class, $requiredMethod)) { + $this->markTestSkipped(sprintf('Method "%s::%s()" does not exist.', Builder::class, $requiredMethod)); + } + $builder = $build(self::getBuilder()); $this->assertInstanceOf(Builder::class, $builder); $mql = $builder->toMql(); @@ -750,32 +755,44 @@ function (Builder $builder) { yield 'whereLike' => [ ['find' => [['name' => new Regex('^1$', 'i')], []]], - fn (Builder $builder) => $builder->whereLike('name', '1'), + fn + (Builder $builder) => $builder->whereLike('name', '1'), + 'whereLike', ]; yield 'whereLike case not sensitive' => [ ['find' => [['name' => new Regex('^1$', 'i')], []]], - fn (Builder $builder) => $builder->whereLike('name', '1', false), + fn + (Builder $builder) => $builder->whereLike('name', '1', false), + 'whereLike', ]; yield 'whereLike case sensitive' => [ ['find' => [['name' => new Regex('^1$', '')], []]], - fn (Builder $builder) => $builder->whereLike('name', '1', true), + fn + (Builder $builder) => $builder->whereLike('name', '1', true), + 'whereLike', ]; yield 'whereNotLike' => [ ['find' => [['name' => ['$not' => new Regex('^1$', 'i')]], []]], - fn (Builder $builder) => $builder->whereNotLike('name', '1'), + fn + (Builder $builder) => $builder->whereNotLike('name', '1'), + 'whereNotLike', ]; yield 'whereNotLike case not sensitive' => [ ['find' => [['name' => ['$not' => new Regex('^1$', 'i')]], []]], - fn (Builder $builder) => $builder->whereNotLike('name', '1', false), + fn + (Builder $builder) => $builder->whereNotLike('name', '1', false), + 'whereNotLike', ]; yield 'whereNotLike case sensitive' => [ ['find' => [['name' => ['$not' => new Regex('^1$', '')]], []]], - fn (Builder $builder) => $builder->whereNotLike('name', '1', true), + fn + (Builder $builder) => $builder->whereNotLike('name', '1', true), + 'whereNotLike', ]; $regex = new Regex('^acme$', 'si'); @@ -1191,142 +1208,154 @@ function (Builder $elemMatchQuery): void { ]; // Method added in Laravel v10.47.0 - if (method_exists(Builder::class, 'whereAll')) { - /** @see DatabaseQueryBuilderTest::testWhereAll */ - yield 'whereAll' => [ - [ - 'find' => [ - ['$and' => [['last_name' => 'Doe'], ['email' => 'Doe']]], - [], // options - ], + /** @see DatabaseQueryBuilderTest::testWhereAll */ + yield 'whereAll' => [ + [ + 'find' => [ + ['$and' => [['last_name' => 'Doe'], ['email' => 'Doe']]], + [], // options ], - fn(Builder $builder) => $builder->whereAll(['last_name', 'email'], 'Doe'), - ]; - - yield 'whereAll operator' => [ - [ - 'find' => [ - [ - '$and' => [ - ['last_name' => ['$not' => new Regex('^.*Doe.*$', 'i')]], - ['email' => ['$not' => new Regex('^.*Doe.*$', 'i')]], - ], + ], + fn + (Builder $builder) => $builder->whereAll(['last_name', 'email'], 'Doe'), + 'whereAll', + ]; + + yield 'whereAll operator' => [ + [ + 'find' => [ + [ + '$and' => [ + ['last_name' => ['$not' => new Regex('^.*Doe.*$', 'i')]], + ['email' => ['$not' => new Regex('^.*Doe.*$', 'i')]], ], - [], // options ], + [], // options ], - fn(Builder $builder) => $builder->whereAll(['last_name', 'email'], 'not like', '%Doe%'), - ]; - - /** @see DatabaseQueryBuilderTest::testOrWhereAll */ - yield 'orWhereAll' => [ - [ - 'find' => [ - [ - '$or' => [ - ['first_name' => 'John'], - ['$and' => [['last_name' => 'Doe'], ['email' => 'Doe']]], - ], + ], + fn + (Builder $builder) => $builder->whereAll(['last_name', 'email'], 'not like', '%Doe%'), + 'whereAll', + ]; + + /** @see DatabaseQueryBuilderTest::testOrWhereAll */ + yield 'orWhereAll' => [ + [ + 'find' => [ + [ + '$or' => [ + ['first_name' => 'John'], + ['$and' => [['last_name' => 'Doe'], ['email' => 'Doe']]], ], - [], // options ], + [], // options ], - fn(Builder $builder) => $builder - ->where('first_name', 'John') - ->orWhereAll(['last_name', 'email'], 'Doe'), - ]; - - yield 'orWhereAll operator' => [ - [ - 'find' => [ - [ - '$or' => [ - ['first_name' => new Regex('^.*John.*$', 'i')], - [ - '$and' => [ - ['last_name' => ['$not' => new Regex('^.*Doe.*$', 'i')]], - ['email' => ['$not' => new Regex('^.*Doe.*$', 'i')]], - ], + ], + fn + (Builder $builder) => $builder + ->where('first_name', 'John') + ->orWhereAll(['last_name', 'email'], 'Doe'), + 'orWhereAll', + ]; + + yield 'orWhereAll operator' => [ + [ + 'find' => [ + [ + '$or' => [ + ['first_name' => new Regex('^.*John.*$', 'i')], + [ + '$and' => [ + ['last_name' => ['$not' => new Regex('^.*Doe.*$', 'i')]], + ['email' => ['$not' => new Regex('^.*Doe.*$', 'i')]], ], ], ], - [], // options ], + [], // options ], - fn(Builder $builder) => $builder - ->where('first_name', 'like', '%John%') - ->orWhereAll(['last_name', 'email'], 'not like', '%Doe%'), - ]; - } + ], + fn + (Builder $builder) => $builder + ->where('first_name', 'like', '%John%') + ->orWhereAll(['last_name', 'email'], 'not like', '%Doe%'), + 'orWhereAll', + ]; // Method added in Laravel v10.47.0 - if (method_exists(Builder::class, 'whereAny')) { - /** @see DatabaseQueryBuilderTest::testWhereAny */ - yield 'whereAny' => [ - [ - 'find' => [ - ['$or' => [['last_name' => 'Doe'], ['email' => 'Doe']]], - [], // options - ], + /** @see DatabaseQueryBuilderTest::testWhereAny */ + yield 'whereAny' => [ + [ + 'find' => [ + ['$or' => [['last_name' => 'Doe'], ['email' => 'Doe']]], + [], // options ], - fn(Builder $builder) => $builder->whereAny(['last_name', 'email'], 'Doe'), - ]; - - yield 'whereAny operator' => [ - [ - 'find' => [ - [ - '$or' => [ - ['last_name' => ['$not' => new Regex('^.*Doe.*$', 'i')]], - ['email' => ['$not' => new Regex('^.*Doe.*$', 'i')]], - ], + ], + fn + (Builder $builder) => $builder->whereAny(['last_name', 'email'], 'Doe'), + 'whereAny', + ]; + + yield 'whereAny operator' => [ + [ + 'find' => [ + [ + '$or' => [ + ['last_name' => ['$not' => new Regex('^.*Doe.*$', 'i')]], + ['email' => ['$not' => new Regex('^.*Doe.*$', 'i')]], ], - [], // options ], + [], // options ], - fn(Builder $builder) => $builder->whereAny(['last_name', 'email'], 'not like', '%Doe%'), - ]; - - /** @see DatabaseQueryBuilderTest::testOrWhereAny */ - yield 'orWhereAny' => [ - [ - 'find' => [ - [ - '$or' => [ - ['first_name' => 'John'], - ['$or' => [['last_name' => 'Doe'], ['email' => 'Doe']]], - ], + ], + fn + (Builder $builder) => $builder->whereAny(['last_name', 'email'], 'not like', '%Doe%'), + 'whereAny', + ]; + + /** @see DatabaseQueryBuilderTest::testOrWhereAny */ + yield 'orWhereAny' => [ + [ + 'find' => [ + [ + '$or' => [ + ['first_name' => 'John'], + ['$or' => [['last_name' => 'Doe'], ['email' => 'Doe']]], ], - [], // options ], + [], // options ], - fn(Builder $builder) => $builder - ->where('first_name', 'John') - ->orWhereAny(['last_name', 'email'], 'Doe'), - ]; - - yield 'orWhereAny operator' => [ - [ - 'find' => [ - [ - '$or' => [ - ['first_name' => new Regex('^.*John.*$', 'i')], - [ - '$or' => [ - ['last_name' => ['$not' => new Regex('^.*Doe.*$', 'i')]], - ['email' => ['$not' => new Regex('^.*Doe.*$', 'i')]], - ], + ], + fn + (Builder $builder) => $builder + ->where('first_name', 'John') + ->orWhereAny(['last_name', 'email'], 'Doe'), + 'whereAny', + ]; + + yield 'orWhereAny operator' => [ + [ + 'find' => [ + [ + '$or' => [ + ['first_name' => new Regex('^.*John.*$', 'i')], + [ + '$or' => [ + ['last_name' => ['$not' => new Regex('^.*Doe.*$', 'i')]], + ['email' => ['$not' => new Regex('^.*Doe.*$', 'i')]], ], ], ], - [], // options ], + [], // options ], - fn(Builder $builder) => $builder - ->where('first_name', 'like', '%John%') - ->orWhereAny(['last_name', 'email'], 'not like', '%Doe%'), - ]; - } + ], + fn + (Builder $builder) => $builder + ->where('first_name', 'like', '%John%') + ->orWhereAny(['last_name', 'email'], 'not like', '%Doe%'), + 'orWhereAny', + ]; } #[DataProvider('provideExceptions')]