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

Commit

Permalink
Apply cast on eloquent queries
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN committed Aug 17, 2023
1 parent 4790125 commit 6946c7a
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 41 deletions.
60 changes: 51 additions & 9 deletions src/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

namespace Jenssegers\Mongodb\Eloquent;

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

/**
* @inheritdoc
*/
public function chunkById($count, callable $callback, $column = '_id', $alias = null)
{
return parent::chunkById($count, $callback, $column, $alias);
}

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -219,6 +214,37 @@ public function getConnection()
return $this->query->getConnection();
}

/**
* @see \Illuminate\Database\Query\Builder::forPageBeforeId()
*/
public function forPageBeforeId($perPage = 15, $lastId = 0, $column = null)
{
if (! $column || $column === $this->model->getKeyName()) {
$column = $this->model->getKeyName();
if ($lastId !== null) {
$lastId = $this->model->castKeyForDatabase($lastId);
}
}

return parent::forPageBeforeId($perPage, $lastId, $column);
}

/**
* @see \Illuminate\Database\Query\Builder::forPageAfterId()
* @see BuildsQueries::chunkById() for usage
*/
public function forPageAfterId($perPage = 15, $lastId = 0, $column = null)
{
if (! $column || $column === $this->model->getKeyName()) {
$column = $this->model->getKeyName();
if ($lastId !== null) {
$lastId = $this->model->castKeyForDatabase($lastId);
}
}

return parent::forPageAfterId($perPage, $lastId, $column);
}

/**
* @inheritdoc
*/
Expand All @@ -244,6 +270,22 @@ protected function ensureOrderForCursorPagination($shouldReverse = false)

public function whereKey($id)
{
return parent::whereKey($this->model->convertKey($id));
if ($id instanceof Model) {
$id = $id->getKey();
}

if (is_array($id) || $id instanceof Arrayable) {
$id = array_map(function ($id) {
return $this->model->castKeyForDatabase($id);
}, $id);

$this->query->whereIn($this->model->getQualifiedKeyName(), $id);

return $this;
}

$id = $this->model->castKeyForDatabase($id);

return $this->where($this->model->getQualifiedKeyName(), '=', $id);
}
}
28 changes: 24 additions & 4 deletions src/Eloquent/Casts/ObjectId.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Jenssegers\Mongodb\Eloquent\Model;
use MongoDB\BSON\ObjectId as BSONObjectId;
use MongoDB\Driver\Exception\InvalidArgumentException;

/**
* Store the value as an ObjectId in the database. This cast should be used for _id fields.
* The value read from the database will not be transformed.
*
* @extends CastsAttributes<BSONObjectId, BSONObjectId>
*/
class ObjectId implements CastsAttributes
{
/**
* Cast the given value.
* Nothing will be done here, the value should already be an ObjectId in the database.
*
* @param Model $model
* @param string $key
Expand All @@ -19,25 +27,37 @@ class ObjectId implements CastsAttributes
*/
public function get($model, string $key, $value, array $attributes)
{
if (! $value instanceof BSONObjectId) {
return $value;
if ($value instanceof BSONObjectId && $model->getKeyName() === $key && $model->getKeyType() === 'string') {
return (string) $value;
}

return (string) $value;
return $value;
}

/**
* Prepare the given value for storage.
* The value will be converted to an ObjectId.
*
* @param Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
*
* @throws \RuntimeException when the value is not an ObjectID or a valid ID string.
*/
public function set($model, string $key, $value, array $attributes)
{
$value = $value instanceof BSONObjectId ? $value : new BSONObjectId($value);
if (! $value instanceof BSONObjectId) {
if (! is_string($value)) {
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)));
}
try {
$value = new BSONObjectId($value);
} catch (InvalidArgumentException $e) {
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);
}
}

return [$key => $value];
}
Expand Down
49 changes: 44 additions & 5 deletions src/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,24 @@ public function getAttribute($key)
return parent::getAttribute($key);
}

protected function throwMissingAttributeExceptionIfApplicable($key)
{
// Fallback to "_id" if "id" is not set.
if ($key === 'id') {
// Should be deprecated?
return $this->getAttribute('_id');
}

parent::throwMissingAttributeExceptionIfApplicable($key);
}

/**
* @inheritdoc
*/
protected function getAttributeFromArray($key)
{
// Support keys in dot notation.
if (Str::contains($key, '.')) {
if (str_contains($key, '.')) {
return Arr::get($this->attributes, $key);
}

Expand Down Expand Up @@ -238,7 +249,7 @@ public function drop($columns)
}

// Perform unset only on current document
return $this->newQuery()->where($this->getKeyName(), $this->getKey())->unset($columns);
return $this->newQuery()->whereKey($this->getKey())->unset($columns);
}

/**
Expand Down Expand Up @@ -449,6 +460,26 @@ protected function isGuardableColumn($key)
return true;
}

/**
* @see \Jenssegers\Mongodb\Query\Builder::whereIn()
* Add a "where in" clause to the query.
*
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
* @param mixed $values
* @param string $boolean
* @param bool $not
* @return $this
*/
public function whereIn($column, $values, $boolean = 'and', $not = false)
{
$args = func_get_args();
if ($column === $this->getKeyName()) {
$args[1] = array_map(fn ($value) => $this->castKeyForDatabase($value), $args[1]);
}

return parent::__call(__FUNCTION__, $args);
}

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -517,13 +548,21 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt
}

/** @internal */
public function convertKey($value)
public function castKeyForDatabase($value)
{
if (! $this->hasCast($this->primaryKey)) {
$key = $this->primaryKey;

if (! $this->hasCast($key)) {
return $value;
}

if (! $this->isClassCastable($key)) {
return $value;
}
$caster = $this->resolveCasterClass($key);
$attributes = $this->normalizeCastClassResponse($key, $caster->set($this, $key, $value, []));

return $this->castAttribute($this->primaryKey, $value);
return $attributes[$key];
}

protected function getClassCastableAttributeValue($key, $value)
Expand Down
17 changes: 6 additions & 11 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
use Illuminate\Support\LazyCollection;
use Illuminate\Support\Str;
use Jenssegers\Mongodb\Connection;
use MongoDB\BSON\Binary;
use MongoDB\BSON\ObjectID;
use MongoDB\BSON\Regex;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Driver\Cursor;
Expand Down Expand Up @@ -188,7 +186,7 @@ public function hint($index)
*/
public function find($id, $columns = [])
{
return parent::find($id, $columns);
return $this->where('_id', '=', $id)->first($columns);
}

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

/**
* @inheritdoc
*/
public function chunkById($count, callable $callback, $column = '_id', $alias = null)
{
return parent::chunkById($count, $callback, $column, $alias);
}

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -907,6 +897,11 @@ public function where($column, $operator = null, $value = null, $boolean = 'and'
return parent::where(...$params);
}

protected function defaultKeyName(): string
{
return '_id';
}

/**
* Compile the where array.
*
Expand Down
25 changes: 13 additions & 12 deletions tests/ModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ public function testInsert(): void

$this->assertTrue(isset($user->_id));
$this->assertIsString($user->_id);
$this->assertNotEquals('', (string) $user->_id);
$this->assertNotEquals(0, strlen((string) $user->_id));
$this->assertNotEquals('', $user->_id);
$this->assertNotEquals(0, strlen($user->_id));
$this->assertInstanceOf(Carbon::class, $user->created_at);

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

$user = new User;
$user->_id = 'customId';
$user->_id = '64d9e303455918c12204cfb5';
$user->name = 'John Doe';
$user->title = 'admin';
$user->age = 35;
$user->save();

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

$raw = $user->getAttributes();
$this->assertIsString($raw['_id']);
Expand Down Expand Up @@ -275,7 +275,8 @@ public function testDestroy(): void
$user->age = 35;
$user->save();

User::destroy((string) $user->_id);
$this->assertIsString($user->_id);
User::destroy($user->_id);

$this->assertEquals(0, User::count());
}
Expand Down Expand Up @@ -396,9 +397,9 @@ public static function provideId(): iterable
yield 'ObjectID' => [
'model' => User::class,
'id' => $objectId,
'expected' => $objectId,
// Not found as the keyType is "string"
'expectedFound' => false,
// $keyType is string, so the ObjectID caster convert to string
'expected' => (string) $objectId,
'expectedFound' => true,
];

$binaryUuid = new Binary(hex2bin('0c103357380648c9a84b867dcb625cfb'), Binary::TYPE_UUID);
Expand Down Expand Up @@ -466,7 +467,7 @@ public function testToArray(): void
$this->assertEquals(['_id', 'created_at', 'name', 'type', 'updated_at'], $keys);
$this->assertIsString($array['created_at']);
$this->assertIsString($array['updated_at']);
$this->assertIsString($array['_id']);
$this->assertInstanceOf(ObjectID::class, $array['_id']);
}

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

$this->assertEquals(['tag1', 'tag1', 'tag2'], $user->tags);
$user = User::where('_id', $user->_id)->first();
$user = User::find($user->_id);
$this->assertEquals(['tag1', 'tag1', 'tag2'], $user->tags);

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

$this->assertEquals(['tag2'], $user->tags);
$user = User::where('_id', $user->_id)->first();
$user = User::find($user->_id)->first();
$this->assertEquals(['tag2'], $user->tags);

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

$this->assertEquals([], $user->tags);
$user = User::where('_id', $user->_id)->first();
$user = User::find($user->_id)->first();
$this->assertEquals([], $user->tags);
}

Expand Down

0 comments on commit 6946c7a

Please sign in to comment.