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

Commit 20968bb

Browse files
committed
Apply cast on eloquent queries
1 parent 4790125 commit 20968bb

File tree

5 files changed

+140
-41
lines changed

5 files changed

+140
-41
lines changed

src/Eloquent/Builder.php

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
namespace Jenssegers\Mongodb\Eloquent;
44

5+
use Illuminate\Contracts\Support\Arrayable;
6+
use Illuminate\Database\Concerns\BuildsQueries;
57
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
8+
use Illuminate\Database\Eloquent\Model;
69
use Jenssegers\Mongodb\Helpers\QueriesRelationships;
710
use MongoDB\Driver\Cursor;
811
use MongoDB\Model\BSONDocument;
@@ -153,14 +156,6 @@ public function decrement($column, $amount = 1, array $extra = [])
153156
return parent::decrement($column, $amount, $extra);
154157
}
155158

156-
/**
157-
* @inheritdoc
158-
*/
159-
public function chunkById($count, callable $callback, $column = '_id', $alias = null)
160-
{
161-
return parent::chunkById($count, $callback, $column, $alias);
162-
}
163-
164159
/**
165160
* @inheritdoc
166161
*/
@@ -219,6 +214,37 @@ public function getConnection()
219214
return $this->query->getConnection();
220215
}
221216

217+
/**
218+
* @see \Illuminate\Database\Query\Builder::forPageBeforeId()
219+
*/
220+
public function forPageBeforeId($perPage = 15, $lastId = 0, $column = null)
221+
{
222+
if (!$column || $column === $this->model->getKeyName()) {
223+
$column = $this->model->getKeyName();
224+
if ($lastId !== null) {
225+
$lastId = $this->model->castKeyForDatabase($lastId);
226+
}
227+
}
228+
229+
return parent::forPageBeforeId($perPage, $lastId, $column);
230+
}
231+
232+
/**
233+
* @see \Illuminate\Database\Query\Builder::forPageAfterId()
234+
* @see BuildsQueries::chunkById() for usage
235+
*/
236+
public function forPageAfterId($perPage = 15, $lastId = 0, $column = null)
237+
{
238+
if (!$column || $column === $this->model->getKeyName()) {
239+
$column = $this->model->getKeyName();
240+
if ($lastId !== null) {
241+
$lastId = $this->model->castKeyForDatabase($lastId);
242+
}
243+
}
244+
245+
return parent::forPageAfterId($perPage, $lastId, $column);
246+
}
247+
222248
/**
223249
* @inheritdoc
224250
*/
@@ -244,6 +270,22 @@ protected function ensureOrderForCursorPagination($shouldReverse = false)
244270

245271
public function whereKey($id)
246272
{
247-
return parent::whereKey($this->model->convertKey($id));
273+
if ($id instanceof Model) {
274+
$id = $id->getKey();
275+
}
276+
277+
if (is_array($id) || $id instanceof Arrayable) {
278+
$id = array_map(function ($id) {
279+
return $this->model->castKeyForDatabase($id);
280+
}, $id);
281+
282+
$this->query->whereIn($this->model->getQualifiedKeyName(), $id);
283+
284+
return $this;
285+
}
286+
287+
$id = $this->model->castKeyForDatabase($id);
288+
289+
return $this->where($this->model->getQualifiedKeyName(), '=', $id);
248290
}
249291
}

src/Eloquent/Casts/ObjectId.php

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@
55
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
66
use Jenssegers\Mongodb\Eloquent\Model;
77
use MongoDB\BSON\ObjectId as BSONObjectId;
8+
use MongoDB\Driver\Exception\InvalidArgumentException;
89

10+
/**
11+
* Store the value as an ObjectId in the database. This cast should be used for _id fields.
12+
* The value read from the database will not be transformed.
13+
*
14+
* @extends CastsAttributes<BSONObjectId, BSONObjectId>
15+
*/
916
class ObjectId implements CastsAttributes
1017
{
1118
/**
1219
* Cast the given value.
20+
* Nothing will be done here, the value should already be an ObjectId in the database.
1321
*
1422
* @param Model $model
1523
* @param string $key
@@ -19,25 +27,37 @@ class ObjectId implements CastsAttributes
1927
*/
2028
public function get($model, string $key, $value, array $attributes)
2129
{
22-
if (! $value instanceof BSONObjectId) {
23-
return $value;
30+
if ($value instanceof BSONObjectId && $model->getKeyName() === $key && $model->getKeyType() === 'string') {
31+
return (string)$value;
2432
}
2533

26-
return (string) $value;
34+
return $value;
2735
}
2836

2937
/**
3038
* Prepare the given value for storage.
39+
* The value will be converted to an ObjectId.
3140
*
3241
* @param Model $model
3342
* @param string $key
3443
* @param mixed $value
3544
* @param array $attributes
3645
* @return mixed
46+
*
47+
* @throws \RuntimeException when the value is not an ObjectID or a valid ID string.
3748
*/
3849
public function set($model, string $key, $value, array $attributes)
3950
{
40-
$value = $value instanceof BSONObjectId ? $value : new BSONObjectId($value);
51+
if (! $value instanceof BSONObjectId) {
52+
if (! is_string($value)) {
53+
throw new \RuntimeException(sprintf('Invalid BSON ObjectID provided for %s[%s]. "string" or %s expected, got "%s". Remove the ObjectId cast if you need to store other types of values.', get_class($model), $key, BSONObjectId::class, get_debug_type($value)));
54+
}
55+
try {
56+
$value = new BSONObjectId($value);
57+
} catch (InvalidArgumentException $e) {
58+
throw new \RuntimeException(sprintf('Invalid BSON ObjectID provided for %s[%s]: %s. Remove the ObjectID cast if you need to store string values.', get_class($model), $key, $value), 0, $e);
59+
}
60+
}
4161

4262
return [$key => $value];
4363
}

src/Eloquent/Model.php

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Jenssegers\Mongodb\Eloquent;
44

5+
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
6+
use Illuminate\Support\Collection as BaseCollection;
57
use function array_key_exists;
68
use DateTimeInterface;
79
use function explode;
@@ -145,13 +147,24 @@ public function getAttribute($key)
145147
return parent::getAttribute($key);
146148
}
147149

150+
protected function throwMissingAttributeExceptionIfApplicable($key)
151+
{
152+
// Fallback to "_id" if "id" is not set.
153+
if ($key === 'id') {
154+
// Should be deprecated?
155+
return $this->getAttribute('_id');
156+
}
157+
158+
parent::throwMissingAttributeExceptionIfApplicable($key);
159+
}
160+
148161
/**
149162
* @inheritdoc
150163
*/
151164
protected function getAttributeFromArray($key)
152165
{
153166
// Support keys in dot notation.
154-
if (Str::contains($key, '.')) {
167+
if (str_contains($key, '.')) {
155168
return Arr::get($this->attributes, $key);
156169
}
157170

@@ -238,7 +251,7 @@ public function drop($columns)
238251
}
239252

240253
// Perform unset only on current document
241-
return $this->newQuery()->where($this->getKeyName(), $this->getKey())->unset($columns);
254+
return $this->newQuery()->whereKey($this->getKey())->unset($columns);
242255
}
243256

244257
/**
@@ -449,6 +462,26 @@ protected function isGuardableColumn($key)
449462
return true;
450463
}
451464

465+
/**
466+
* @see \Jenssegers\Mongodb\Query\Builder::whereIn()
467+
* Add a "where in" clause to the query.
468+
*
469+
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
470+
* @param mixed $values
471+
* @param string $boolean
472+
* @param bool $not
473+
* @return $this
474+
*/
475+
public function whereIn($column, $values, $boolean = 'and', $not = false)
476+
{
477+
$args = func_get_args();
478+
if ($column === $this->getKeyName()) {
479+
$args[1] = array_map(fn ($value) => $this->castKeyForDatabase($value), $args[1]);
480+
}
481+
482+
return parent::__call(__FUNCTION__, $args);
483+
}
484+
452485
/**
453486
* @inheritdoc
454487
*/
@@ -517,13 +550,21 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt
517550
}
518551

519552
/** @internal */
520-
public function convertKey($value)
553+
public function castKeyForDatabase($value)
521554
{
522-
if (! $this->hasCast($this->primaryKey)) {
555+
$key = $this->primaryKey;
556+
557+
if (! $this->hasCast($key)) {
558+
return $value;
559+
}
560+
561+
if (!$this->isClassCastable($key)) {
523562
return $value;
524563
}
564+
$caster = $this->resolveCasterClass($key);
565+
$attributes = $this->normalizeCastClassResponse($key, $caster->set($this, $key, $value, []));
525566

526-
return $this->castAttribute($this->primaryKey, $value);
567+
return $attributes[$key];
527568
}
528569

529570
protected function getClassCastableAttributeValue($key, $value)

src/Query/Builder.php

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
use Illuminate\Support\LazyCollection;
1313
use Illuminate\Support\Str;
1414
use Jenssegers\Mongodb\Connection;
15-
use MongoDB\BSON\Binary;
16-
use MongoDB\BSON\ObjectID;
1715
use MongoDB\BSON\Regex;
1816
use MongoDB\BSON\UTCDateTime;
1917
use MongoDB\Driver\Cursor;
@@ -188,7 +186,7 @@ public function hint($index)
188186
*/
189187
public function find($id, $columns = [])
190188
{
191-
return parent::find($id, $columns);
189+
return $this->where('_id', '=', $id)->first($columns);
192190
}
193191

194192
/**
@@ -657,14 +655,6 @@ public function decrement($column, $amount = 1, array $extra = [], array $option
657655
return $this->increment($column, -1 * $amount, $extra, $options);
658656
}
659657

660-
/**
661-
* @inheritdoc
662-
*/
663-
public function chunkById($count, callable $callback, $column = '_id', $alias = null)
664-
{
665-
return parent::chunkById($count, $callback, $column, $alias);
666-
}
667-
668658
/**
669659
* @inheritdoc
670660
*/
@@ -907,6 +897,11 @@ public function where($column, $operator = null, $value = null, $boolean = 'and'
907897
return parent::where(...$params);
908898
}
909899

900+
protected function defaultKeyName(): string
901+
{
902+
return '_id';
903+
}
904+
910905
/**
911906
* Compile the where array.
912907
*

tests/ModelTest.php

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ public function testInsert(): void
6262

6363
$this->assertTrue(isset($user->_id));
6464
$this->assertIsString($user->_id);
65-
$this->assertNotEquals('', (string) $user->_id);
66-
$this->assertNotEquals(0, strlen((string) $user->_id));
65+
$this->assertNotEquals('', $user->_id);
66+
$this->assertNotEquals(0, strlen($user->_id));
6767
$this->assertInstanceOf(Carbon::class, $user->created_at);
6868

6969
$raw = $user->getAttributes();
@@ -123,14 +123,14 @@ public function testManualStringId(): void
123123
$this->assertInstanceOf(ObjectID::class, $raw['_id']);
124124

125125
$user = new User;
126-
$user->_id = 'customId';
126+
$user->_id = '64d9e303455918c12204cfb5';
127127
$user->name = 'John Doe';
128128
$user->title = 'admin';
129129
$user->age = 35;
130130
$user->save();
131131

132132
$this->assertTrue($user->exists);
133-
$this->assertEquals('customId', $user->_id);
133+
$this->assertEquals('64d9e303455918c12204cfb5', $user->_id);
134134

135135
$raw = $user->getAttributes();
136136
$this->assertIsString($raw['_id']);
@@ -275,7 +275,8 @@ public function testDestroy(): void
275275
$user->age = 35;
276276
$user->save();
277277

278-
User::destroy((string) $user->_id);
278+
$this->assertIsString($user->_id);
279+
User::destroy($user->_id);
279280

280281
$this->assertEquals(0, User::count());
281282
}
@@ -396,9 +397,9 @@ public static function provideId(): iterable
396397
yield 'ObjectID' => [
397398
'model' => User::class,
398399
'id' => $objectId,
399-
'expected' => $objectId,
400-
// Not found as the keyType is "string"
401-
'expectedFound' => false,
400+
// $keyType is string, so the ObjectID caster convert to string
401+
'expected' => (string) $objectId,
402+
'expectedFound' => true,
402403
];
403404

404405
$binaryUuid = new Binary(hex2bin('0c103357380648c9a84b867dcb625cfb'), Binary::TYPE_UUID);
@@ -466,7 +467,7 @@ public function testToArray(): void
466467
$this->assertEquals(['_id', 'created_at', 'name', 'type', 'updated_at'], $keys);
467468
$this->assertIsString($array['created_at']);
468469
$this->assertIsString($array['updated_at']);
469-
$this->assertIsString($array['_id']);
470+
$this->assertInstanceOf(ObjectID::class, $array['_id']);
470471
}
471472

472473
public function testUnset(): void
@@ -719,20 +720,20 @@ public function testPushPull(): void
719720
$user->push('tags', 'tag2', true);
720721

721722
$this->assertEquals(['tag1', 'tag1', 'tag2'], $user->tags);
722-
$user = User::where('_id', $user->_id)->first();
723+
$user = User::find($user->_id);
723724
$this->assertEquals(['tag1', 'tag1', 'tag2'], $user->tags);
724725

725726
$user->pull('tags', 'tag1');
726727

727728
$this->assertEquals(['tag2'], $user->tags);
728-
$user = User::where('_id', $user->_id)->first();
729+
$user = User::find($user->_id)->first();
729730
$this->assertEquals(['tag2'], $user->tags);
730731

731732
$user->push('tags', 'tag3');
732733
$user->pull('tags', ['tag2', 'tag3']);
733734

734735
$this->assertEquals([], $user->tags);
735-
$user = User::where('_id', $user->_id)->first();
736+
$user = User::find($user->_id)->first();
736737
$this->assertEquals([], $user->tags);
737738
}
738739

0 commit comments

Comments
 (0)