Skip to content

Commit b55fddb

Browse files
committed
PHPORM-100 Support query on numerical field names
1 parent 1af8b9d commit b55fddb

File tree

4 files changed

+50
-8
lines changed

4 files changed

+50
-8
lines changed

src/Eloquent/Model.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ public function getAttribute($key)
169169
return null;
170170
}
171171

172+
$key = (string) $key;
173+
172174
// An unset attribute is null or throw an exception.
173175
if (isset($this->unset[$key])) {
174176
return $this->throwMissingAttributeExceptionIfApplicable($key);
@@ -194,6 +196,8 @@ public function getAttribute($key)
194196
/** @inheritdoc */
195197
protected function getAttributeFromArray($key)
196198
{
199+
$key = (string) $key;
200+
197201
// Support keys in dot notation.
198202
if (str_contains($key, '.')) {
199203
return Arr::get($this->attributes, $key);
@@ -212,6 +216,8 @@ public function setAttribute($key, $value)
212216
$value = $builder->convertKey($value);
213217
}
214218

219+
$key = (string) $key;
220+
215221
// Support keys in dot notation.
216222
if (str_contains($key, '.')) {
217223
// Store to a temporary key, then move data to the actual key
@@ -314,6 +320,8 @@ public function originalIsEquivalent($key)
314320
/** @inheritdoc */
315321
public function offsetUnset($offset): void
316322
{
323+
$offset = (string) $offset;
324+
317325
if (str_contains($offset, '.')) {
318326
// Update the field in the subdocument
319327
Arr::forget($this->attributes, $offset);

src/Query/Builder.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,11 @@
2323
use MongoDB\BSON\UTCDateTime;
2424
use MongoDB\Driver\Cursor;
2525
use RuntimeException;
26-
use Stringable;
2726

2827
use function array_fill_keys;
2928
use function array_is_list;
3029
use function array_key_exists;
3130
use function array_merge;
32-
use function array_merge_recursive;
3331
use function array_values;
3432
use function array_walk_recursive;
3533
use function assert;
@@ -47,6 +45,7 @@
4745
use function in_array;
4846
use function is_array;
4947
use function is_int;
48+
use function is_scalar;
5049
use function is_string;
5150
use function md5;
5251
use function preg_match;
@@ -60,6 +59,7 @@
6059
use function strlen;
6160
use function strtolower;
6261
use function substr;
62+
use function var_export;
6363

6464
class Builder extends BaseBuilder
6565
{
@@ -660,7 +660,7 @@ public function update(array $values, array $options = [])
660660
{
661661
// Use $set as default operator for field names that are not in an operator
662662
foreach ($values as $key => $value) {
663-
if (str_starts_with($key, '$')) {
663+
if (is_string($key) && str_starts_with($key, '$')) {
664664
continue;
665665
}
666666

@@ -961,8 +961,8 @@ public function where($column, $operator = null, $value = null, $boolean = 'and'
961961
}
962962
}
963963

964-
if (func_num_args() === 1 && is_string($column)) {
965-
throw new ArgumentCountError(sprintf('Too few arguments to function %s("%s"), 1 passed and at least 2 expected when the 1st is a string.', __METHOD__, $column));
964+
if (func_num_args() === 1 && is_scalar($column)) {
965+
throw new ArgumentCountError(sprintf('Too few arguments to function %s(%s), 1 passed and at least 2 expected when the 1st is a scalar.', __METHOD__, var_export($column, true)));
966966
}
967967

968968
return parent::where(...$params);
@@ -993,7 +993,7 @@ protected function compileWheres(): array
993993
}
994994

995995
// Convert column name to string to use as array key
996-
if (isset($where['column']) && $where['column'] instanceof Stringable) {
996+
if (isset($where['column'])) {
997997
$where['column'] = (string) $where['column'];
998998
}
999999

@@ -1071,7 +1071,14 @@ protected function compileWheres(): array
10711071
}
10721072

10731073
// Merge the compiled where with the others.
1074-
$compiled = array_merge_recursive($compiled, $result);
1074+
// array_merge_recursive can't be used here because it converts int keys to sequential int.
1075+
foreach ($result as $key => $value) {
1076+
if (in_array($key, ['$and', '$or', '$nor'])) {
1077+
$compiled[$key] = array_merge($compiled[$key] ?? [], $value);
1078+
} else {
1079+
$compiled[$key] = $value;
1080+
}
1081+
}
10751082
}
10761083

10771084
return $compiled;

tests/ModelTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,4 +957,20 @@ public function testEnumCast(): void
957957
$this->assertSame(MemberStatus::Member->value, $check->getRawOriginal('member_status'));
958958
$this->assertSame(MemberStatus::Member, $check->member_status);
959959
}
960+
961+
public function testNumericFieldName(): void
962+
{
963+
$user = new User();
964+
$user->{1} = 'one';
965+
$user->{2} = ['2' => 'two.two'];
966+
$user->save();
967+
968+
$found = User::where(1, 'one')->first();
969+
$this->assertInstanceOf(User::class, $found);
970+
$this->assertEquals('one', $found[1]);
971+
972+
$found = User::where('2.2', 'two.two')->first();
973+
$this->assertInstanceOf(User::class, $found);
974+
$this->assertEquals([2 => 'two.two'], $found[2]);
975+
}
960976
}

tests/Query/BuilderTest.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ public static function provideQueryBuilderToMql(): iterable
9090
fn (Builder $builder) => $builder->where('foo', 'bar'),
9191
];
9292

93+
yield 'find with numeric field name' => [
94+
['find' => [['123' => 'bar'], []]],
95+
fn (Builder $builder) => $builder->where(123, 'bar'),
96+
];
97+
9398
yield 'where with single array of conditions' => [
9499
[
95100
'find' => [
@@ -1175,10 +1180,16 @@ public static function provideExceptions(): iterable
11751180

11761181
yield 'find with single string argument' => [
11771182
ArgumentCountError::class,
1178-
'Too few arguments to function MongoDB\Laravel\Query\Builder::where("foo"), 1 passed and at least 2 expected when the 1st is a string',
1183+
'Too few arguments to function MongoDB\Laravel\Query\Builder::where(\'foo\'), 1 passed and at least 2 expected when the 1st is a scalar',
11791184
fn (Builder $builder) => $builder->where('foo'),
11801185
];
11811186

1187+
yield 'find with single numeric argument' => [
1188+
ArgumentCountError::class,
1189+
'Too few arguments to function MongoDB\Laravel\Query\Builder::where(123), 1 passed and at least 2 expected when the 1st is a scalar',
1190+
fn (Builder $builder) => $builder->where(123),
1191+
];
1192+
11821193
yield 'where regex not starting with /' => [
11831194
LogicException::class,
11841195
'Missing expected starting delimiter in regular expression "^ac/me$", supported delimiters are: / # ~',

0 commit comments

Comments
 (0)