diff --git a/src/Eloquent/Builder.php b/src/Eloquent/Builder.php index 84e93b83..0d7816e3 100644 --- a/src/Eloquent/Builder.php +++ b/src/Eloquent/Builder.php @@ -241,4 +241,9 @@ protected function ensureOrderForCursorPagination($shouldReverse = false) ]; })->values(); } + + public function whereKey($id) + { + return parent::whereKey($this->model->convertKey($id)); + } } diff --git a/src/Eloquent/Casts/ObjectId.php b/src/Eloquent/Casts/ObjectId.php index bf34bea2..3961d23d 100644 --- a/src/Eloquent/Casts/ObjectId.php +++ b/src/Eloquent/Casts/ObjectId.php @@ -37,10 +37,8 @@ public function get($model, string $key, $value, array $attributes) */ public function set($model, string $key, $value, array $attributes) { - if ($value instanceof BSONObjectId) { - return $value; - } + $value = $value instanceof BSONObjectId ? $value : new BSONObjectId($value); - return new BSONObjectId($value); + return [$key => $value]; } } diff --git a/src/Eloquent/Model.php b/src/Eloquent/Model.php index 2d985f62..320f8102 100644 --- a/src/Eloquent/Model.php +++ b/src/Eloquent/Model.php @@ -15,8 +15,6 @@ use Illuminate\Support\Str; use function in_array; use Jenssegers\Mongodb\Query\Builder as QueryBuilder; -use MongoDB\BSON\Binary; -use MongoDB\BSON\ObjectID; use MongoDB\BSON\UTCDateTime; use function uniqid; @@ -52,30 +50,6 @@ abstract class Model extends BaseModel */ protected $parentRelation; - /** - * Custom accessor for the model's id. - * - * @param mixed $value - * @return mixed - */ - public function getIdAttribute($value = null) - { - // If we don't have a value for 'id', we will use the MongoDB '_id' value. - // This allows us to work with models in a more sql-like way. - if (! $value && array_key_exists('_id', $this->attributes)) { - $value = $this->attributes['_id']; - } - - // Convert ObjectID to string. - if ($value instanceof ObjectID) { - return (string) $value; - } elseif ($value instanceof Binary) { - return (string) $value->getData(); - } - - return $value; - } - /** * @inheritdoc */ @@ -190,12 +164,7 @@ protected function getAttributeFromArray($key) public function setAttribute($key, $value) { // Convert _id to ObjectID. - if ($key == '_id' && is_string($value)) { - $builder = $this->newBaseQueryBuilder(); - - $value = $builder->convertKey($value); - } // Support keys in dot notation. - elseif (Str::contains($key, '.')) { + if (Str::contains($key, '.')) { // Store to a temporary key, then move data to the actual key $uniqueKey = uniqid($key); parent::setAttribute($uniqueKey, $value); @@ -209,28 +178,6 @@ public function setAttribute($key, $value) return parent::setAttribute($key, $value); } - /** - * @inheritdoc - */ - public function attributesToArray() - { - $attributes = parent::attributesToArray(); - - // Because the original Eloquent never returns objects, we convert - // MongoDB related objects to a string representation. This kind - // of mimics the SQL behaviour so that dates are formatted - // nicely when your models are converted to JSON. - foreach ($attributes as $key => &$value) { - if ($value instanceof ObjectID) { - $value = (string) $value; - } elseif ($value instanceof Binary) { - $value = (string) $value->getData(); - } - } - - return $attributes; - } - /** * @inheritdoc */ @@ -568,4 +515,23 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt return $attributes; } + + /** @internal */ + public function convertKey($value) + { + if (! $this->hasCast($this->primaryKey)) { + return $value; + } + + return $this->castAttribute($this->primaryKey, $value); + } + + protected function getClassCastableAttributeValue($key, $value) + { + // The class cast cache does not play nice with database values that + // already are objects, so we need to manually unset it + unset($this->classCastCache[$key]); + + return parent::getClassCastableAttributeValue($key, $value); + } } diff --git a/src/Query/Builder.php b/src/Query/Builder.php index dd448ed0..1b361502 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -188,7 +188,7 @@ public function hint($index) */ public function find($id, $columns = []) { - return $this->where('_id', '=', $this->convertKey($id))->first($columns); + return parent::find($id, $columns); } /** @@ -884,25 +884,6 @@ protected function performUpdate($query, array $options = []) return 0; } - /** - * Convert a key to ObjectID if needed. - * - * @param mixed $id - * @return mixed - */ - public function convertKey($id) - { - if (is_string($id) && strlen($id) === 24 && ctype_xdigit($id)) { - return new ObjectID($id); - } - - if (is_string($id) && strlen($id) === 16 && preg_match('~[^\x20-\x7E\t\r\n]~', $id) > 0) { - return new Binary($id, Binary::TYPE_UUID); - } - - return $id; - } - /** * @inheritdoc */ @@ -950,19 +931,6 @@ protected function compileWheres(): array } } - // Convert id's. - if (isset($where['column']) && ($where['column'] == '_id' || Str::endsWith($where['column'], '._id'))) { - // Multiple values. - if (isset($where['values'])) { - foreach ($where['values'] as &$value) { - $value = $this->convertKey($value); - } - } // Single value. - elseif (isset($where['value'])) { - $where['value'] = $this->convertKey($where['value']); - } - } - // Convert DateTime values to UTCDateTime. if (isset($where['value'])) { if (is_array($where['value'])) { diff --git a/src/Relations/EmbedsOneOrMany.php b/src/Relations/EmbedsOneOrMany.php index dedae591..929baef9 100644 --- a/src/Relations/EmbedsOneOrMany.php +++ b/src/Relations/EmbedsOneOrMany.php @@ -245,8 +245,7 @@ protected function getForeignKeyValue($id) $id = $id->getKey(); } - // Convert the id to MongoId if necessary. - return $this->toBase()->convertKey($id); + return $id; } /** diff --git a/tests/EmbeddedRelationsTest.php b/tests/EmbeddedRelationsTest.php index 972572cc..34d2af6b 100644 --- a/tests/EmbeddedRelationsTest.php +++ b/tests/EmbeddedRelationsTest.php @@ -60,7 +60,7 @@ public function testEmbedsManySave() $this->assertInstanceOf(DateTime::class, $address->created_at); $this->assertInstanceOf(DateTime::class, $address->updated_at); $this->assertNotNull($address->_id); - $this->assertIsString($address->_id); + $this->assertInstanceOf(ObjectId::class, $address->_id); $raw = $address->getAttributes(); $this->assertInstanceOf(ObjectId::class, $raw['_id']); @@ -183,7 +183,7 @@ public function testEmbedsManyCreate() $user = User::create([]); $address = $user->addresses()->create(['city' => 'Bruxelles']); $this->assertInstanceOf(Address::class, $address); - $this->assertIsString($address->_id); + $this->assertInstanceOf(ObjectId::class, $address->_id); $this->assertEquals(['Bruxelles'], $user->addresses->pluck('city')->all()); $raw = $address->getAttributes(); diff --git a/tests/ModelTest.php b/tests/ModelTest.php index 1042a07b..78cc833c 100644 --- a/tests/ModelTest.php +++ b/tests/ModelTest.php @@ -86,6 +86,7 @@ public function testUpdate(): void /** @var User $check */ $check = User::find($user->_id); + $this->assertInstanceOf(User::class, $check); $check->age = 36; $check->save(); @@ -395,22 +396,24 @@ public static function provideId(): iterable yield 'ObjectID' => [ 'model' => User::class, 'id' => $objectId, - 'expected' => (string) $objectId, - 'expectedFound' => true, + 'expected' => $objectId, + // Not found as the keyType is "string" + 'expectedFound' => false, ]; $binaryUuid = new Binary(hex2bin('0c103357380648c9a84b867dcb625cfb'), Binary::TYPE_UUID); yield 'BinaryUuid' => [ 'model' => User::class, 'id' => $binaryUuid, - 'expected' => (string) $binaryUuid, - 'expectedFound' => true, + 'expected' => $binaryUuid, + // Not found as the keyType is "string" + 'expectedFound' => false, ]; yield 'cast as BinaryUuid' => [ 'model' => IdIsBinaryUuid::class, 'id' => $binaryUuid, - 'expected' => (string) $binaryUuid, + 'expected' => $binaryUuid, 'expectedFound' => true, ]; diff --git a/tests/Models/Address.php b/tests/Models/Address.php index 1050eb0e..c05f5757 100644 --- a/tests/Models/Address.php +++ b/tests/Models/Address.php @@ -4,13 +4,20 @@ namespace Jenssegers\Mongodb\Tests\Models; +use Jenssegers\Mongodb\Eloquent\Casts\ObjectId as ObjectIdCast; use Jenssegers\Mongodb\Eloquent\Model as Eloquent; use Jenssegers\Mongodb\Relations\EmbedsMany; +use MongoDB\BSON\ObjectId; class Address extends Eloquent { protected $connection = 'mongodb'; protected static $unguarded = true; + protected $keyType = ObjectId::class; + + protected $casts = [ + '_id' => ObjectIdCast::class, + ]; public function addresses(): EmbedsMany { diff --git a/tests/Models/User.php b/tests/Models/User.php index f559af47..e3f1420e 100644 --- a/tests/Models/User.php +++ b/tests/Models/User.php @@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Str; +use Jenssegers\Mongodb\Eloquent\Casts\ObjectId as ObjectIdCast; use Jenssegers\Mongodb\Eloquent\HybridRelations; use Jenssegers\Mongodb\Eloquent\Model as Eloquent; @@ -38,6 +39,7 @@ class User extends Eloquent implements AuthenticatableContract, CanResetPassword protected $connection = 'mongodb'; protected $casts = [ + '_id' => ObjectIdCast::class, 'birthday' => 'datetime', 'entry.date' => 'datetime', 'member_status' => MemberStatus::class,