From 1ae0d1975582ad388fe093184ac1e7cc34d9719c Mon Sep 17 00:00:00 2001 From: Steve Axtmann Date: Fri, 18 May 2018 15:17:35 +0200 Subject: [PATCH 01/11] added objectid to the casts array --- src/Jenssegers/Mongodb/Eloquent/Model.php | 31 +++++++++++++++-- src/Jenssegers/Mongodb/Query/Builder.php | 42 +++++++++++++++++++++-- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/Jenssegers/Mongodb/Eloquent/Model.php b/src/Jenssegers/Mongodb/Eloquent/Model.php index 894ebe41a..ea1d508ba 100644 --- a/src/Jenssegers/Mongodb/Eloquent/Model.php +++ b/src/Jenssegers/Mongodb/Eloquent/Model.php @@ -165,7 +165,7 @@ protected function getAttributeFromArray($key) public function setAttribute($key, $value) { // Convert _id to ObjectID. - if ($key == '_id' && is_string($value)) { + if (is_string($value) && ($key === '_id' || $this->isObjectId($key))) { $builder = $this->newBaseQueryBuilder(); $value = $builder->convertKey($value); @@ -411,7 +411,7 @@ protected function newBaseQueryBuilder() { $connection = $this->getConnection(); - return new QueryBuilder($connection, $connection->getPostProcessor()); + return (new QueryBuilder($connection, $connection->getPostProcessor()))->casts($this->casts); } /** @@ -480,4 +480,31 @@ public function __call($method, $parameters) return parent::__call($method, $parameters); } + + /** + * @inheritdoc + */ + protected function castAttribute($key, $value) + { + if (is_null($value)) { + return $value; + } + + if ($this->getCastType($key) === 'objectid') { + return (string) $value; + } + + return parent::castAttribute($key, $value); + } + + /** + * Is the field an ObjectId + * + * @param string $key + * @return bool + */ + protected function isObjectId($key) + { + return $this->hasCast($key, ['objectid']); + } } diff --git a/src/Jenssegers/Mongodb/Query/Builder.php b/src/Jenssegers/Mongodb/Query/Builder.php index c425f5165..b59f8db87 100644 --- a/src/Jenssegers/Mongodb/Query/Builder.php +++ b/src/Jenssegers/Mongodb/Query/Builder.php @@ -52,6 +52,13 @@ class Builder extends BaseBuilder */ public $options = []; + /** + * An array of properties that need to be cast to their original type + * + * @var array + */ + public $casts = []; + /** * Indicate if we are executing a pagination query. * @@ -811,7 +818,7 @@ public function drop($columns) */ public function newQuery() { - return new Builder($this->connection, $this->processor); + return (new Builder($this->connection, $this->processor))->casts($this->casts); } /** @@ -907,7 +914,14 @@ protected function compileWheres() } // Convert id's. - if (isset($where['column']) && ($where['column'] == '_id' || Str::endsWith($where['column'], '._id'))) { + if ( + isset($where['column']) + && ( + $where['column'] == '_id' + || Str::endsWith($where['column'], '._id') + || $this->isCastableToObjectId($where['column']) + ) + ) { // Multiple values. if (isset($where['values'])) { foreach ($where['values'] as &$value) { @@ -1144,6 +1158,19 @@ public function options(array $options) return $this; } + /** + * Set the casts for the query + * + * @param array $casts + * @return $this + */ + public function casts(array $casts) + { + $this->casts = $casts; + + return $this; + } + /** * @inheritdoc */ @@ -1155,4 +1182,15 @@ public function __call($method, $parameters) return parent::__call($method, $parameters); } + + /** + * Should the given field be casted to an ObjectId + * + * @param string $field + * @return boolean + */ + private function isCastableToObjectId($field) + { + return isset($this->casts[$field]) && $this->casts[$field] === 'objectid'; + } } From e86e2f561fe9847b7d0e0fefadec522ee49db0a3 Mon Sep 17 00:00:00 2001 From: Steve Axtmann Date: Fri, 18 May 2018 15:45:12 +0200 Subject: [PATCH 02/11] documentation --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 0aa40e5c7..24865e038 100644 --- a/README.md +++ b/README.md @@ -711,6 +711,25 @@ $users = User::where('birthday', '>', new DateTime('-18 years'))->get(); ### Relations +By default, relationships are not stored as Object Ids. If you need relationships to be stored as ObjectIds, you can specify that they should be by adding them to the $casts array: + +```php +use Jenssegers\Mongodb\Eloquent\Model as Eloquent; + +class User extends Eloquent { + + protected $casts = [ + 'item_id' => 'objectid' // 'objectid' has to be lowercase! + ]; + + public function items() + { + return $this->hasMany('Item'); + } + +} +``` + Supported relations are: - hasOne From 8c5fbaf7c161e970c8bc8572593a0a8515ca53e6 Mon Sep 17 00:00:00 2001 From: Steve Axtmann Date: Fri, 18 May 2018 15:48:21 +0200 Subject: [PATCH 03/11] removed unnecessary space --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 24865e038..a86dc1e72 100644 --- a/README.md +++ b/README.md @@ -711,7 +711,7 @@ $users = User::where('birthday', '>', new DateTime('-18 years'))->get(); ### Relations -By default, relationships are not stored as Object Ids. If you need relationships to be stored as ObjectIds, you can specify that they should be by adding them to the $casts array: +By default, relationships are not stored as ObjectIds. If you need relationships to be stored as ObjectIds, you can specify that they should be by adding them to the $casts array: ```php use Jenssegers\Mongodb\Eloquent\Model as Eloquent; From ac77d91cffe202546d1949f506f038acf1be639f Mon Sep 17 00:00:00 2001 From: Steve Axtmann Date: Thu, 24 May 2018 14:25:18 +0200 Subject: [PATCH 04/11] added handling for arrays of objectids --- src/Jenssegers/Mongodb/Eloquent/Model.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Jenssegers/Mongodb/Eloquent/Model.php b/src/Jenssegers/Mongodb/Eloquent/Model.php index ea1d508ba..116c60f50 100644 --- a/src/Jenssegers/Mongodb/Eloquent/Model.php +++ b/src/Jenssegers/Mongodb/Eloquent/Model.php @@ -165,10 +165,16 @@ protected function getAttributeFromArray($key) public function setAttribute($key, $value) { // Convert _id to ObjectID. - if (is_string($value) && ($key === '_id' || $this->isObjectId($key))) { + if ((is_string($value) && $key === '_id') || $this->isObjectId($key)) { $builder = $this->newBaseQueryBuilder(); - $value = $builder->convertKey($value); + if (is_array($value)) { + foreach ($value as &$val) { + $val = $builder->convertKey($val); + } + } else { + $value = $builder->convertKey($value); + } } // Support keys in dot notation. elseif (Str::contains($key, '.')) { if (in_array($key, $this->getDates()) && $value) { @@ -491,6 +497,14 @@ protected function castAttribute($key, $value) } if ($this->getCastType($key) === 'objectid') { + if (is_array($value)) { + foreach ($value as &$val) { + $val = (string) $val; + } + + return $value; + } + return (string) $value; } From 7a7a31e7ef3b9fcde7063eebcc98610edce5d0f1 Mon Sep 17 00:00:00 2001 From: Steve Axtmann Date: Thu, 24 May 2018 14:32:05 +0200 Subject: [PATCH 05/11] improved docs example --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a86dc1e72..4073209df 100644 --- a/README.md +++ b/README.md @@ -719,12 +719,12 @@ use Jenssegers\Mongodb\Eloquent\Model as Eloquent; class User extends Eloquent { protected $casts = [ - 'item_id' => 'objectid' // 'objectid' has to be lowercase! + 'account_id' => 'objectid' // 'objectid' has to be lowercase! ]; - public function items() + public function account() { - return $this->hasMany('Item'); + return $this->belongsTo('Account'); } } From 6b25b17804ab54cd8d46a4f90db4b3f773c324c5 Mon Sep 17 00:00:00 2001 From: ScottSpittle Date: Wed, 6 Jun 2018 09:52:19 +0200 Subject: [PATCH 06/11] Ensure $casts are obeyed for pushing values --- src/Jenssegers/Mongodb/Eloquent/Model.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Jenssegers/Mongodb/Eloquent/Model.php b/src/Jenssegers/Mongodb/Eloquent/Model.php index 116c60f50..0abe295fb 100644 --- a/src/Jenssegers/Mongodb/Eloquent/Model.php +++ b/src/Jenssegers/Mongodb/Eloquent/Model.php @@ -299,6 +299,15 @@ public function push() $this->pushAttributeValues($column, $values, $unique); + // Convert $casts on push() + if($this->isObjectId($column)) { + foreach ($values as &$value) { + if(is_string($value)) { + $value = new ObjectId($value); + } + } + } + return $query->push($column, $values, $unique); } From 204472dd380f91a59c0632d482c3906264eaadbf Mon Sep 17 00:00:00 2001 From: ScottSpittle Date: Wed, 6 Jun 2018 10:17:29 +0200 Subject: [PATCH 07/11] Ensure $casts are obeyed for where query building --- src/Jenssegers/Mongodb/Query/Builder.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Jenssegers/Mongodb/Query/Builder.php b/src/Jenssegers/Mongodb/Query/Builder.php index b59f8db87..2cbecdf15 100644 --- a/src/Jenssegers/Mongodb/Query/Builder.php +++ b/src/Jenssegers/Mongodb/Query/Builder.php @@ -875,6 +875,22 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' } } + // Convert $casts + if($this->isCastableToObjectId($column)) + { + if(is_array($params[2])) { + foreach ($params[2] as &$value) { + if(is_string($value)) { + $value = new ObjectId($value); + } + } + } else { + if(is_string($params[2])) { + $params[2] = new ObjectId($params[2]); + } + } + } + return call_user_func_array('parent::where', $params); } From fd027b3a116938f6846f8957a5adb946bda70e21 Mon Sep 17 00:00:00 2001 From: Steve Axtmann Date: Mon, 11 Jun 2018 09:46:00 +0200 Subject: [PATCH 08/11] added support for full date casting, push(), arrays, date casting --- src/Jenssegers/Mongodb/Eloquent/Model.php | 249 +++++++++++++++++++--- 1 file changed, 220 insertions(+), 29 deletions(-) diff --git a/src/Jenssegers/Mongodb/Eloquent/Model.php b/src/Jenssegers/Mongodb/Eloquent/Model.php index 0abe295fb..486c14c15 100644 --- a/src/Jenssegers/Mongodb/Eloquent/Model.php +++ b/src/Jenssegers/Mongodb/Eloquent/Model.php @@ -97,6 +97,10 @@ protected function asDateTime($value) return Carbon::createFromTimestamp($value->toDateTime()->getTimestamp()); } + if ($value instanceof Carbon) { + return $value; + } + return parent::asDateTime($value); } @@ -159,34 +163,95 @@ protected function getAttributeFromArray($key) return parent::getAttributeFromArray($key); } + /** + * Is the given array an associative array + * + * @param array $arr + * + * @return bool + */ + private function isAssoc(array $arr) + { + // if it's empty, we presume that it's sequential + if ([] === $arr) { + return false; + } + + // if $arr[0] does not exist, it must to be a associative array + if (!array_key_exists(0, $arr)) { + return true; + } + + // strict compare the array keys against a range of the same length + return array_keys($arr) !== range(0, count($arr) - 1); + } + + /** + * Flatten an associative array of attributes to dot notation + * + * @param string $key + * @param $value + * + * @return \Illuminate\Support\Collection + */ + private function flattenAttributes($key, $value) + { + $values = collect(); + + if (is_array($value) && $this->isAssoc($value)) { + foreach ($value as $k => $value) { + $values = $values->merge($this->flattenAttributes("$key.$k", $value)); + } + } else { + $values->put($key, $value); + } + + return $values; + } + /** * @inheritdoc */ public function setAttribute($key, $value) { - // Convert _id to ObjectID. - if ((is_string($value) && $key === '_id') || $this->isObjectId($key)) { - $builder = $this->newBaseQueryBuilder(); + $values = $this->flattenAttributes($key, $value); - if (is_array($value)) { - foreach ($value as &$val) { + $values->each(function ($val, $k) { + // Convert to ObjectID. + if ($this->isCastableToObjectId($k)) { + $builder = $this->newBaseQueryBuilder(); + + if (is_array($val)) { + foreach ($val as &$v) { + $v = $builder->convertKey($v); + } + } else { $val = $builder->convertKey($val); } - } else { - $value = $builder->convertKey($value); - } - } // Support keys in dot notation. - elseif (Str::contains($key, '.')) { - if (in_array($key, $this->getDates()) && $value) { - $value = $this->fromDateTime($value); + } // Convert to UTCDateTime + else { + if (in_array($k, $this->getDates()) && $val) { + if (is_array($val)) { + foreach ($val as &$v) { + $v = $this->fromDateTime($v); + } + } else { + $val = $this->fromDateTime($val); + } + } } - Arr::set($this->attributes, $key, $value); + // Support keys in dot notation. + if (Str::contains($k, '.')) { + Arr::set($this->attributes, $k, $val); - return; - } + return; + } - return parent::setAttribute($key, $value); + parent::setAttribute($k, $val); + }); + + return $this; } /** @@ -194,24 +259,48 @@ public function setAttribute($key, $value) */ public function attributesToArray() { + // Convert dates before parent method call so that all dates have the same format + foreach ($this->getDates() as $key) { + if (!Arr::has($this->attributes, $key)) { + continue; + } + + $val = Arr::get($this->attributes, $key); + + if (is_array($val)) { + foreach ($val as &$v) { + $v = $this->asDateTime($v)->format('Y-m-d H:i:s.v'); + } + } elseif ($val) { + $val = $this->asDateTime($val)->format('Y-m-d H:i:s.v'); + } + + Arr::set($this->attributes, $key, $val); + } + $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; + $this->getObjectIds()->each(function ($key) use (&$attributes) { + if (!Arr::has($attributes, $key)) { + return; } - } - // Convert dot-notation dates. - foreach ($this->getDates() as $key) { - if (Str::contains($key, '.') && Arr::has($attributes, $key)) { - Arr::set($attributes, $key, (string) $this->asDateTime(Arr::get($attributes, $key))); + $value = Arr::get($attributes, $key); + + if (is_array($value)) { + foreach ($value as &$val) { + $val = (string)$val; + } + } else { + $value = (string)$value; } - } + + Arr::set($attributes, $key, $value); + }); return $attributes; } @@ -224,6 +313,18 @@ public function getCasts() return $this->casts; } + /** + * Get a list of the castable ObjectId fields + * + * @return \Illuminate\Support\Collection + */ + public function getObjectIds() + { + return collect($this->getCasts())->filter(function ($val) { + return 'objectid' === $val; + })->keys()->push('_id'); + } + /** * @inheritdoc */ @@ -300,7 +401,7 @@ public function push() $this->pushAttributeValues($column, $values, $unique); // Convert $casts on push() - if($this->isObjectId($column)) { + if ($this->isCastableToObjectId($column)) { foreach ($values as &$value) { if(is_string($value)) { $value = new ObjectId($value); @@ -308,6 +409,13 @@ public function push() } } + // Convert dates on push() + if (in_array($column, $this->getDates())) { + foreach ($values as &$value) { + $value = $this->fromDateTime($value); + } + } + return $query->push($column, $values, $unique); } @@ -353,11 +461,94 @@ protected function pushAttributeValues($column, array $values, $unique = false) $current[] = $value; } - $this->attributes[$column] = $current; + // Support for dot notation keys + if (Str::contains($column, '.')) { + Arr::set($this->attributes, $column, $current); + } else { + $this->attributes[$column] = $current; + } $this->syncOriginalAttribute($column); } + /** + * @inheritdoc + */ + public function syncOriginalAttribute($attribute) + { + // Support for dot notation keys + if (Str::contains($attribute, '.')) { + $value = Arr::get($this->attributes, $attribute); + + Arr::set($this->original, $attribute, $value); + } else { + $this->original[$attribute] = $this->attributes[$attribute]; + } + + return $this; + } + + /** + * @inheritdoc + */ + protected function addDateAttributesToArray(array $attributes) + { + foreach ($this->getDates() as $key) { + if (!isset($attributes[$key])) { + continue; + } + + // Support for array of dates + if (is_array($attributes[$key])) { + foreach ($attributes[$key] as &$value) { + $value = $this->serializeDate($this->asDateTime($value)); + } + } else { + $attributes[$key] = $this->serializeDate($this->asDateTime($attributes[$key])); + } + } + + return $attributes; + } + + /** + * @inheritdoc + */ + public function getAttributeValue($key) + { + $value = $this->getAttributeFromArray($key); + + // If the attribute is listed as a date, we will convert it to a DateTime + // instance on retrieval, which makes it quite convenient to work with + // date fields without having to create a mutator for each property. + if (in_array($key, $this->getDates()) && !is_null($value)) { + if (is_array($value)) { + foreach ($value as &$val) { + $val = $this->asDateTime($val); + } + } else { + $value = $this->asDateTime($value); + } + + return $value; + } + + // Also convert objectIds to strings + if ($this->getObjectIds()->search($key) !== false && !is_null($value)) { + if (is_array($value)) { + foreach ($value as &$val) { + $val = (string) $val; + } + } else { + $value = (string) $value; + } + + return $value; + } + + return parent::getAttributeFromArray($key); + } + /** * Remove one or more values to the underlying attribute value and sync with original. * @@ -526,8 +717,8 @@ protected function castAttribute($key, $value) * @param string $key * @return bool */ - protected function isObjectId($key) + protected function isCastableToObjectId($key) { - return $this->hasCast($key, ['objectid']); + return '_id' === $key || $this->hasCast($key, ['objectid']); } } From 4f7fd0f58c7a2667267ee078624727e6a1932f74 Mon Sep 17 00:00:00 2001 From: Steve Axtmann Date: Mon, 11 Jun 2018 09:49:49 +0200 Subject: [PATCH 09/11] styleci fixes --- src/Jenssegers/Mongodb/Eloquent/Model.php | 6 +++--- src/Jenssegers/Mongodb/Query/Builder.php | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Jenssegers/Mongodb/Eloquent/Model.php b/src/Jenssegers/Mongodb/Eloquent/Model.php index 486c14c15..fffce4058 100644 --- a/src/Jenssegers/Mongodb/Eloquent/Model.php +++ b/src/Jenssegers/Mongodb/Eloquent/Model.php @@ -293,10 +293,10 @@ public function attributesToArray() if (is_array($value)) { foreach ($value as &$val) { - $val = (string)$val; + $val = (string) $val; } } else { - $value = (string)$value; + $value = (string) $value; } Arr::set($attributes, $key, $value); @@ -403,7 +403,7 @@ public function push() // Convert $casts on push() if ($this->isCastableToObjectId($column)) { foreach ($values as &$value) { - if(is_string($value)) { + if (is_string($value)) { $value = new ObjectId($value); } } diff --git a/src/Jenssegers/Mongodb/Query/Builder.php b/src/Jenssegers/Mongodb/Query/Builder.php index 2cbecdf15..b8d17d16e 100644 --- a/src/Jenssegers/Mongodb/Query/Builder.php +++ b/src/Jenssegers/Mongodb/Query/Builder.php @@ -876,16 +876,15 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' } // Convert $casts - if($this->isCastableToObjectId($column)) - { - if(is_array($params[2])) { + if ($this->isCastableToObjectId($column)) { + if (is_array($params[2])) { foreach ($params[2] as &$value) { - if(is_string($value)) { + if (is_string($value)) { $value = new ObjectId($value); } } } else { - if(is_string($params[2])) { + if (is_string($params[2])) { $params[2] = new ObjectId($params[2]); } } From 4e67ac4c7fc862bfa997293b731caabb8246c698 Mon Sep 17 00:00:00 2001 From: Steve Axtmann Date: Wed, 27 Jun 2018 10:20:44 +0200 Subject: [PATCH 10/11] pulled changes from upstream --- .travis.yml | 18 +++--------------- docker/Dockerfile => Dockerfile | 4 ++-- docker-compose.yml | 4 ++-- docker/entrypoint.sh | 3 --- 4 files changed, 7 insertions(+), 22 deletions(-) rename docker/Dockerfile => Dockerfile (86%) delete mode 100755 docker/entrypoint.sh diff --git a/.travis.yml b/.travis.yml index 30972bfb5..a6358266b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,24 +9,12 @@ services: - docker install: - # Update docker-engine using Ubuntu 'trusty' apt repo - - > - curl -sSL "https://get.docker.com/gpg" | - sudo -E apt-key add - - - > - echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" | - sudo tee -a /etc/apt/sources.list - - sudo apt-get update - - > - sudo apt-get -o Dpkg::Options::="--force-confdef" \ - -o Dpkg::Options::="--force-confold" --assume-yes install docker-engine --allow-unauthenticated - docker version - - # Update docker-compose via pip - sudo pip install docker-compose - docker-compose version - - docker-compose up --build -d - - docker ps -a + - sed -i -e "s/php:cli/php:${TRAVIS_PHP_VERSION}-cli/g" Dockerfile + - cat Dockerfile + - docker-compose build script: - docker-compose up --exit-code-from php diff --git a/docker/Dockerfile b/Dockerfile similarity index 86% rename from docker/Dockerfile rename to Dockerfile index 62c68e45d..03c2ba7e2 100644 --- a/docker/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:7.1-cli +FROM php:cli RUN pecl install xdebug @@ -11,4 +11,4 @@ RUN curl -sS https://getcomposer.org/installer | php \ && mv composer.phar /usr/local/bin/ \ && ln -s /usr/local/bin/composer.phar /usr/local/bin/composer -ENV PATH="~/.composer/vendor/bin:./vendor/bin:${PATH}" \ No newline at end of file +ENV PATH="~/.composer/vendor/bin:./vendor/bin:${PATH}" diff --git a/docker-compose.yml b/docker-compose.yml index ce5c95652..c1d83dd97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,11 +6,11 @@ services: container_name: php build: context: . - dockerfile: docker/Dockerfile + dockerfile: Dockerfile volumes: - .:/code working_dir: /code - command: docker/entrypoint.sh + command: bash -c "composer install --prefer-source --no-interaction && php ./vendor/bin/phpunit" depends_on: - mysql - mongodb diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100755 index c646f3917..000000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -sleep 3 && composer install --prefer-source --no-interaction && php ./vendor/bin/phpunit From 37e918d549271841d5948a3f9a968797bd13d6d5 Mon Sep 17 00:00:00 2001 From: Steve Axtmann Date: Fri, 6 Jul 2018 16:55:48 +0200 Subject: [PATCH 11/11] fixed closures in where failing --- src/Jenssegers/Mongodb/Query/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jenssegers/Mongodb/Query/Builder.php b/src/Jenssegers/Mongodb/Query/Builder.php index b8d17d16e..7681f3c2f 100644 --- a/src/Jenssegers/Mongodb/Query/Builder.php +++ b/src/Jenssegers/Mongodb/Query/Builder.php @@ -876,7 +876,7 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' } // Convert $casts - if ($this->isCastableToObjectId($column)) { + if (!($column instanceof \Closure) && $this->isCastableToObjectId($column)) { if (is_array($params[2])) { foreach ($params[2] as &$value) { if (is_string($value)) {