Skip to content
This repository was archived by the owner on Aug 22, 2023. It is now read-only.

Commit 22a8bce

Browse files
committed
Fix consecutive _ in like
1 parent 6729f52 commit 22a8bce

File tree

3 files changed

+40
-9
lines changed

3 files changed

+40
-9
lines changed

src/Query/Builder.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ protected function compileWheres(): array
944944
$where['operator'] = strtolower($where['operator']);
945945

946946
// Convert aliased operators
947-
if (array_key_exists($where['operator'], $this->conversion)) {
947+
if (isset($this->conversion[$where['operator']])) {
948948
$where['operator'] = $this->conversion[$where['operator']];
949949
}
950950
}
@@ -1036,20 +1036,30 @@ protected function compileWhereBasic(array $where): array
10361036

10371037
// Replace like with a Regex instance.
10381038
if (in_array($operator, ['like', 'not like'])) {
1039-
// Convert % and _ to regex, and unescape \% and \_
10401039
$regex = preg_replace(
1041-
['#(^|[^\\\])%#', '#(^|[^\\\])_#', '#\\\\\\\(%|_)#'],
1040+
[
1041+
// Unescaped % are converted to .*
1042+
// Group consecutive %
1043+
'#(^|[^\\\])%+#',
1044+
// Unescaped _ are converted to .
1045+
// Use positive lookahead to replace consecutive _
1046+
'#(?<=^|[^\\\\])_#',
1047+
// Escaped \% or \_ are unescaped
1048+
'#\\\\\\\(%|_)#',
1049+
],
10421050
['$1.*', '$1.', '$1'],
1051+
// Escape any regex reserved characters, so they are matched
1052+
// All backslashes are converted to \\, which are needed in matching regexes.
10431053
preg_quote($value),
10441054
);
10451055
$value = new Regex('^'.$regex.'$', 'i');
10461056

1047-
// For inverse like operations, we can just use the $not operator and pass it a Regex instance.
1057+
// For inverse like operations, we can just use the $not operator with the Regex
10481058
$operator = $operator === 'like' ? '=' : 'not';
10491059
}
10501060

10511061
// Manipulate regex operations.
1052-
if (in_array($operator, ['regex', 'not regex'])) {
1062+
elseif (in_array($operator, ['regex', 'not regex'])) {
10531063
// Automatically convert regular expression strings to Regex objects.
10541064
if (is_string($value)) {
10551065
// Detect the delimiter and validate the preg pattern
@@ -1070,7 +1080,7 @@ protected function compileWhereBasic(array $where): array
10701080
$value = new Regex($regstr, $flags);
10711081
}
10721082

1073-
// For inverse regex operations, we can just use the $not operator and pass it a Regex instance.
1083+
// For inverse regex operations, we can just use the $not operatorwith the Regex
10741084
$operator = $operator === 'regex' ? '=' : 'not';
10751085
}
10761086

tests/Query/BuilderTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -601,12 +601,12 @@ function (Builder $builder) {
601601

602602
yield 'where like %' => [
603603
['find' => [['name' => new Regex('^.*ac.*me.*$', 'i')], []]],
604-
fn (Builder $builder) => $builder->where('name', 'like', '%ac%me%'),
604+
fn (Builder $builder) => $builder->where('name', 'like', '%ac%%me%'),
605605
];
606606

607607
yield 'where like _' => [
608-
['find' => [['name' => new Regex('^.ac.me.$', 'i')], []]],
609-
fn (Builder $builder) => $builder->where('name', 'like', '_ac_me_'),
608+
['find' => [['name' => new Regex('^.ac..me.$', 'i')], []]],
609+
fn (Builder $builder) => $builder->where('name', 'like', '_ac__me_'),
610610
];
611611

612612
$regex = new Regex('^acme$', 'si');

tests/QueryTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,21 @@ public function testAndWhere(): void
7070
$this->assertCount(2, $users);
7171
}
7272

73+
public function testRegexp(): void
74+
{
75+
User::create(['name' => 'Simple', 'company' => 'acme']);
76+
User::create(['name' => 'With slash', 'company' => 'oth/er']);
77+
78+
$users = User::where('company', 'regexp', '/^acme$/')->get();
79+
$this->assertCount(1, $users);
80+
81+
$users = User::where('company', 'regexp', '/^ACME$/i')->get();
82+
$this->assertCount(1, $users);
83+
84+
$users = User::where('company', 'regexp', '/^oth\/er$/')->get();
85+
$this->assertCount(1, $users);
86+
}
87+
7388
public function testLike(): void
7489
{
7590
$users = User::where('name', 'like', '%doe')->get();
@@ -83,6 +98,12 @@ public function testLike(): void
8398

8499
$users = User::where('name', 'like', 't%')->get();
85100
$this->assertCount(1, $users);
101+
102+
$users = User::where('name', 'like', 'j___ doe')->get();
103+
$this->assertCount(2, $users);
104+
105+
$users = User::where('name', 'like', '_oh_ _o_')->get();
106+
$this->assertCount(1, $users);
86107
}
87108

88109
public function testNotLike(): void

0 commit comments

Comments
 (0)