From d7f2afba8c9350b9fdef9e96a52ebcf8817f309d Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Tue, 21 Feb 2023 10:21:37 +0100 Subject: [PATCH] Use InteractsWithDictionary in Eloquent collection This improves the behaviour of database collections when using non-scalar primary keys by using the same dictionary behaviour used elsewhere. --- .../Database/Eloquent/Collection.php | 15 +++++---- .../DatabaseEloquentCollectionTest.php | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index 79785c67da2d..04cfbb0f2728 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Queue\QueueableCollection; use Illuminate\Contracts\Queue\QueueableEntity; use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary; use Illuminate\Support\Arr; use Illuminate\Support\Collection as BaseCollection; use LogicException; @@ -17,6 +18,8 @@ */ class Collection extends BaseCollection implements QueueableCollection { + use InteractsWithDictionary; + /** * Find a model in the collection by key. * @@ -322,7 +325,7 @@ public function merge($items) $dictionary = $this->getDictionary(); foreach ($items as $item) { - $dictionary[$item->getKey()] = $item; + $dictionary[$this->getDictionaryKey($item->getKey())] = $item; } return new static(array_values($dictionary)); @@ -398,7 +401,7 @@ public function diff($items) $dictionary = $this->getDictionary($items); foreach ($this->items as $item) { - if (! isset($dictionary[$item->getKey()])) { + if (! isset($dictionary[$this->getDictionaryKey($item->getKey())])) { $diff->add($item); } } @@ -423,7 +426,7 @@ public function intersect($items) $dictionary = $this->getDictionary($items); foreach ($this->items as $item) { - if (isset($dictionary[$item->getKey()])) { + if (isset($dictionary[$this->getDictionaryKey($item->getKey())])) { $intersect->add($item); } } @@ -459,7 +462,7 @@ public function only($keys) return new static($this->items); } - $dictionary = Arr::only($this->getDictionary(), $keys); + $dictionary = Arr::only($this->getDictionary(), array_map($this->getDictionaryKey(...), (array) $keys)); return new static(array_values($dictionary)); } @@ -472,7 +475,7 @@ public function only($keys) */ public function except($keys) { - $dictionary = Arr::except($this->getDictionary(), $keys); + $dictionary = Arr::except($this->getDictionary(), array_map($this->getDictionaryKey(...), (array) $keys)); return new static(array_values($dictionary)); } @@ -545,7 +548,7 @@ public function getDictionary($items = null) $dictionary = []; foreach ($items as $value) { - $dictionary[$value->getKey()] = $value; + $dictionary[$this->getDictionaryKey($value->getKey())] = $value; } return $dictionary; diff --git a/tests/Database/DatabaseEloquentCollectionTest.php b/tests/Database/DatabaseEloquentCollectionTest.php index a6863b4e8041..7553e947d0de 100755 --- a/tests/Database/DatabaseEloquentCollectionTest.php +++ b/tests/Database/DatabaseEloquentCollectionTest.php @@ -608,6 +608,25 @@ public function testLoadExistsShouldCastBool() $this->assertContainsOnly('bool', $commentsExists); } + public function testWithNonScalarKey() + { + $fooKey = new EloquentTestKey('foo'); + $foo = m::mock(Model::class); + $foo->shouldReceive('getKey')->andReturn($fooKey); + + $barKey = new EloquentTestKey('bar'); + $bar = m::mock(Model::class); + $bar->shouldReceive('getKey')->andReturn($barKey); + + $collection = new Collection([$foo, $bar]); + + $this->assertCount(1, $collection->only([$fooKey])); + $this->assertSame($foo, $collection->only($fooKey)->first()); + + $this->assertCount(1, $collection->except([$fooKey])); + $this->assertSame($bar, $collection->except($fooKey)->first()); + } + /** * Helpers... */ @@ -689,3 +708,15 @@ class EloquentTestCommentModel extends Model protected $guarded = []; public $timestamps = false; } + +class EloquentTestKey +{ + public function __construct(private readonly string $key) + { + } + + public function __toString() + { + return $this->key; + } +}