diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 1b41b2162d55..f0f535981300 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -498,6 +498,29 @@ public function findOrNew($id, $columns = ['*']) return $this->newModelInstance(); } + /** + * Find a model by its primary key or call a callback. + * + * @param mixed $id + * @param \Closure|array $columns + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|mixed + */ + public function findOr($id, $columns = ['*'], Closure $callback = null) + { + if ($columns instanceof Closure) { + $callback = $columns; + + $columns = ['*']; + } + + if (! is_null($model = $this->find($id, $columns))) { + return $model; + } + + return $callback(); + } + /** * Get the first record matching the attributes or instantiate it. * diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index cda0dc0e3b92..fd73157e16dc 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -711,6 +711,37 @@ public function findOrFail($id, $columns = ['*']) throw (new ModelNotFoundException)->setModel(get_class($this->related), $id); } + /** + * Find a related model by its primary key or call a callback. + * + * @param mixed $id + * @param \Closure|array $columns + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed + */ + public function findOr($id, $columns = ['*'], Closure $callback = null) + { + if ($columns instanceof Closure) { + $callback = $columns; + + $columns = ['*']; + } + + $result = $this->find($id, $columns); + + $id = $id instanceof Arrayable ? $id->toArray() : $id; + + if (is_array($id)) { + if (count($result) === count(array_unique($id))) { + return $result; + } + } elseif (! is_null($result)) { + return $result; + } + + return $callback(); + } + /** * Add a basic where clause to the query, and return the first result. * diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index 74326957364c..893d5fab906b 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -388,6 +388,37 @@ public function findOrFail($id, $columns = ['*']) throw (new ModelNotFoundException)->setModel(get_class($this->related), $id); } + /** + * Find a related model by its primary key or call a callback. + * + * @param mixed $id + * @param \Closure|array $columns + * @param \Closure|null $callback + * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed + */ + public function findOr($id, $columns = ['*'], Closure $callback = null) + { + if ($columns instanceof Closure) { + $callback = $columns; + + $columns = ['*']; + } + + $result = $this->find($id, $columns); + + $id = $id instanceof Arrayable ? $id->toArray() : $id; + + if (is_array($id)) { + if (count($result) === count(array_unique($id))) { + return $result; + } + } elseif (! is_null($result)) { + return $result; + } + + return $callback(); + } + /** * Get the results of the relationship. * diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index 044167daf033..48c350ee5b11 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -154,6 +154,75 @@ public function testFindOrFailMethodWithManyUsingCollectionThrowsModelNotFoundEx $builder->findOrFail(new Collection([1, 2]), ['column']); } + public function testFindOrMethod() + { + $builder = m::mock(Builder::class.'[first]', [$this->getMockQueryBuilder()]); + $model = $this->getMockModel(); + $model->shouldReceive('getKeyType')->andReturn('int'); + $builder->setModel($model); + $builder->getQuery()->shouldReceive('where')->with('foo_table.foo', '=', 1)->twice(); + $builder->getQuery()->shouldReceive('where')->with('foo_table.foo', '=', 2)->once(); + $builder->shouldReceive('first')->andReturn($model)->once(); + $builder->shouldReceive('first')->with(['column'])->andReturn($model)->once(); + $builder->shouldReceive('first')->andReturn(null)->once(); + + $this->assertSame($model, $builder->findOr(1, fn () => 'callback result')); + $this->assertSame($model, $builder->findOr(1, ['column'], fn () => 'callback result')); + $this->assertSame('callback result', $builder->findOr(2, fn () => 'callback result')); + } + + public function testFindOrMethodWithMany() + { + $builder = m::mock(Builder::class.'[get]', [$this->getMockQueryBuilder()]); + $model1 = $this->getMockModel(); + $model2 = $this->getMockModel(); + $builder->setModel($model1); + $builder->getQuery()->shouldReceive('whereIn')->with('foo_table.foo', [1, 2])->twice(); + $builder->getQuery()->shouldReceive('whereIn')->with('foo_table.foo', [1, 2, 3])->once(); + $builder->shouldReceive('get')->andReturn(new Collection([$model1, $model2]))->once(); + $builder->shouldReceive('get')->with(['column'])->andReturn(new Collection([$model1, $model2]))->once(); + $builder->shouldReceive('get')->andReturn(null)->once(); + + $result = $builder->findOr([1, 2], fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame($model1, $result[0]); + $this->assertSame($model2, $result[1]); + + $result = $builder->findOr([1, 2], ['column'], fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame($model1, $result[0]); + $this->assertSame($model2, $result[1]); + + $result = $builder->findOr([1, 2, 3], fn () => 'callback result'); + $this->assertSame('callback result', $result); + } + + public function testFindOrMethodWithManyUsingCollection() + { + $builder = m::mock(Builder::class.'[get]', [$this->getMockQueryBuilder()]); + $model1 = $this->getMockModel(); + $model2 = $this->getMockModel(); + $builder->setModel($model1); + $builder->getQuery()->shouldReceive('whereIn')->with('foo_table.foo', [1, 2])->twice(); + $builder->getQuery()->shouldReceive('whereIn')->with('foo_table.foo', [1, 2, 3])->once(); + $builder->shouldReceive('get')->andReturn(new Collection([$model1, $model2]))->once(); + $builder->shouldReceive('get')->with(['column'])->andReturn(new Collection([$model1, $model2]))->once(); + $builder->shouldReceive('get')->andReturn(null)->once(); + + $result = $builder->findOr(new Collection([1, 2]), fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame($model1, $result[0]); + $this->assertSame($model2, $result[1]); + + $result = $builder->findOr(new Collection([1, 2]), ['column'], fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame($model1, $result[0]); + $this->assertSame($model2, $result[1]); + + $result = $builder->findOr(new Collection([1, 2, 3]), fn () => 'callback result'); + $this->assertSame('callback result', $result); + } + public function testFirstOrFailMethodThrowsModelNotFoundException() { $this->expectException(ModelNotFoundException::class); diff --git a/tests/Database/DatabaseEloquentHasManyThroughIntegrationTest.php b/tests/Database/DatabaseEloquentHasManyThroughIntegrationTest.php index 4aef3e4a595f..9ffcbdd5a453 100644 --- a/tests/Database/DatabaseEloquentHasManyThroughIntegrationTest.php +++ b/tests/Database/DatabaseEloquentHasManyThroughIntegrationTest.php @@ -201,6 +201,80 @@ public function testFindOrFailWithManyUsingCollectionThrowsAnException() HasManyThroughTestCountry::first()->posts()->findOrFail(new Collection([1, 2])); } + public function testFindOrMethod() + { + HasManyThroughTestCountry::create(['id' => 1, 'name' => 'United States of America', 'shortname' => 'us']) + ->users()->create(['id' => 1, 'email' => 'taylorotwell@gmail.com', 'country_short' => 'us']) + ->posts()->create(['id' => 1, 'title' => 'A title', 'body' => 'A body', 'email' => 'taylorotwell@gmail.com']); + + $result = HasManyThroughTestCountry::first()->posts()->findOr(1, fn () => 'callback result'); + $this->assertInstanceOf(HasManyThroughTestPost::class, $result); + $this->assertSame(1, $result->id); + $this->assertSame('A title', $result->title); + + $result = HasManyThroughTestCountry::first()->posts()->findOr(1, ['posts.id'], fn () => 'callback result'); + $this->assertInstanceOf(HasManyThroughTestPost::class, $result); + $this->assertSame(1, $result->id); + $this->assertNull($result->title); + + $result = HasManyThroughTestCountry::first()->posts()->findOr(2, fn () => 'callback result'); + $this->assertSame('callback result', $result); + } + + public function testFindOrMethodWithMany() + { + HasManyThroughTestCountry::create(['id' => 1, 'name' => 'United States of America', 'shortname' => 'us']) + ->users()->create(['id' => 1, 'email' => 'taylorotwell@gmail.com', 'country_short' => 'us']) + ->posts()->createMany([ + ['id' => 1, 'title' => 'A title', 'body' => 'A body', 'email' => 'taylorotwell@gmail.com'], + ['id' => 2, 'title' => 'Another title', 'body' => 'Another body', 'email' => 'taylorotwell@gmail.com'], + ]); + + $result = HasManyThroughTestCountry::first()->posts()->findOr([1, 2], fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame(1, $result[0]->id); + $this->assertSame(2, $result[1]->id); + $this->assertSame('A title', $result[0]->title); + $this->assertSame('Another title', $result[1]->title); + + $result = HasManyThroughTestCountry::first()->posts()->findOr([1, 2], ['posts.id'], fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame(1, $result[0]->id); + $this->assertSame(2, $result[1]->id); + $this->assertNull($result[0]->title); + $this->assertNull($result[1]->title); + + $result = HasManyThroughTestCountry::first()->posts()->findOr([1, 2, 3], fn () => 'callback result'); + $this->assertSame('callback result', $result); + } + + public function testFindOrMethodWithManyUsingCollection() + { + HasManyThroughTestCountry::create(['id' => 1, 'name' => 'United States of America', 'shortname' => 'us']) + ->users()->create(['id' => 1, 'email' => 'taylorotwell@gmail.com', 'country_short' => 'us']) + ->posts()->createMany([ + ['id' => 1, 'title' => 'A title', 'body' => 'A body', 'email' => 'taylorotwell@gmail.com'], + ['id' => 2, 'title' => 'Another title', 'body' => 'Another body', 'email' => 'taylorotwell@gmail.com'], + ]); + + $result = HasManyThroughTestCountry::first()->posts()->findOr(new Collection([1, 2]), fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame(1, $result[0]->id); + $this->assertSame(2, $result[1]->id); + $this->assertSame('A title', $result[0]->title); + $this->assertSame('Another title', $result[1]->title); + + $result = HasManyThroughTestCountry::first()->posts()->findOr(new Collection([1, 2]), ['posts.id'], fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame(1, $result[0]->id); + $this->assertSame(2, $result[1]->id); + $this->assertNull($result[0]->title); + $this->assertNull($result[1]->title); + + $result = HasManyThroughTestCountry::first()->posts()->findOr(new Collection([1, 2, 3]), fn () => 'callback result'); + $this->assertSame('callback result', $result); + } + public function testFirstRetrievesFirstRecord() { $this->seedData(); diff --git a/tests/Integration/Database/EloquentBelongsToManyTest.php b/tests/Integration/Database/EloquentBelongsToManyTest.php index 5f08195ab0bc..1d0b9d5c53f7 100644 --- a/tests/Integration/Database/EloquentBelongsToManyTest.php +++ b/tests/Integration/Database/EloquentBelongsToManyTest.php @@ -413,6 +413,77 @@ public function testFindOrNewMethod() $this->assertInstanceOf(Tag::class, $post->tags()->findOrNew(666)); } + public function testFindOrMethod() + { + $post = Post::create(['title' => Str::random()]); + $post->tags()->create(['name' => Str::random()]); + + $result = $post->tags()->findOr(1, fn () => 'callback result'); + $this->assertInstanceOf(Tag::class, $result); + $this->assertSame(1, $result->id); + $this->assertNotNull($result->name); + + $result = $post->tags()->findOr(1, ['id'], fn () => 'callback result'); + $this->assertInstanceOf(Tag::class, $result); + $this->assertSame(1, $result->id); + $this->assertNull($result->name); + + $result = $post->tags()->findOr(2, fn () => 'callback result'); + $this->assertSame('callback result', $result); + } + + public function testFindOrMethodWithMany() + { + $post = Post::create(['title' => Str::random()]); + $post->tags()->createMany([ + ['name' => Str::random()], + ['name' => Str::random()], + ]); + + $result = $post->tags()->findOr([1, 2], fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame(1, $result[0]->id); + $this->assertSame(2, $result[1]->id); + $this->assertNotNull($result[0]->name); + $this->assertNotNull($result[1]->name); + + $result = $post->tags()->findOr([1, 2], ['id'], fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame(1, $result[0]->id); + $this->assertSame(2, $result[1]->id); + $this->assertNull($result[0]->name); + $this->assertNull($result[1]->name); + + $result = $post->tags()->findOr([1, 2, 3], fn () => 'callback result'); + $this->assertSame('callback result', $result); + } + + public function testFindOrMethodWithManyUsingCollection() + { + $post = Post::create(['title' => Str::random()]); + $post->tags()->createMany([ + ['name' => Str::random()], + ['name' => Str::random()], + ]); + + $result = $post->tags()->findOr(new Collection([1, 2]), fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame(1, $result[0]->id); + $this->assertSame(2, $result[1]->id); + $this->assertNotNull($result[0]->name); + $this->assertNotNull($result[1]->name); + + $result = $post->tags()->findOr(new Collection([1, 2]), ['id'], fn () => 'callback result'); + $this->assertInstanceOf(Collection::class, $result); + $this->assertSame(1, $result[0]->id); + $this->assertSame(2, $result[1]->id); + $this->assertNull($result[0]->name); + $this->assertNull($result[1]->name); + + $result = $post->tags()->findOr(new Collection([1, 2, 3]), fn () => 'callback result'); + $this->assertSame('callback result', $result); + } + public function testFirstOrNewMethod() { $post = Post::create(['title' => Str::random()]);