Skip to content

Commit f4043fe

Browse files
authored
[11.x] Pass collection of models to whereMorphedTo / whereNotMorphedTo (#54324)
* where(Not)MorphedTo with collection * Extra tests * Allow arrays * Fix docblocks
1 parent 48b82c2 commit f4043fe

File tree

2 files changed

+182
-14
lines changed

2 files changed

+182
-14
lines changed

src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php

+30-10
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ public function orWhereMorphDoesntHaveRelation($relation, $types, $column, $oper
536536
* Add a morph-to relationship condition to the query.
537537
*
538538
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
539-
* @param \Illuminate\Database\Eloquent\Model|string|null $model
539+
* @param \Illuminate\Database\Eloquent\Model|iterable<int, \Illuminate\Database\Eloquent\Model>|string|null $model
540540
* @return $this
541541
*/
542542
public function whereMorphedTo($relation, $model, $boolean = 'and')
@@ -559,17 +559,27 @@ public function whereMorphedTo($relation, $model, $boolean = 'and')
559559
return $this->where($relation->qualifyColumn($relation->getMorphType()), $model, null, $boolean);
560560
}
561561

562-
return $this->where(function ($query) use ($relation, $model) {
563-
$query->where($relation->qualifyColumn($relation->getMorphType()), $model->getMorphClass())
564-
->where($relation->qualifyColumn($relation->getForeignKeyName()), $model->getKey());
562+
$models = BaseCollection::wrap($model);
563+
564+
if ($models->isEmpty()) {
565+
throw new InvalidArgumentException('Collection given to whereMorphedTo method may not be empty.');
566+
}
567+
568+
return $this->where(function ($query) use ($relation, $models) {
569+
$models->groupBy(fn ($model) => $model->getMorphClass())->each(function ($models) use ($query, $relation) {
570+
$query->orWhere(function ($query) use ($relation, $models) {
571+
$query->where($relation->qualifyColumn($relation->getMorphType()), $models->first()->getMorphClass())
572+
->whereIn($relation->qualifyColumn($relation->getForeignKeyName()), $models->map->getKey());
573+
});
574+
});
565575
}, null, null, $boolean);
566576
}
567577

568578
/**
569579
* Add a not morph-to relationship condition to the query.
570580
*
571581
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
572-
* @param \Illuminate\Database\Eloquent\Model|string $model
582+
* @param \Illuminate\Database\Eloquent\Model|iterable<int, \Illuminate\Database\Eloquent\Model>|string $model
573583
* @return $this
574584
*/
575585
public function whereNotMorphedTo($relation, $model, $boolean = 'and')
@@ -588,17 +598,27 @@ public function whereNotMorphedTo($relation, $model, $boolean = 'and')
588598
return $this->whereNot($relation->qualifyColumn($relation->getMorphType()), '<=>', $model, $boolean);
589599
}
590600

591-
return $this->whereNot(function ($query) use ($relation, $model) {
592-
$query->where($relation->qualifyColumn($relation->getMorphType()), '<=>', $model->getMorphClass())
593-
->where($relation->qualifyColumn($relation->getForeignKeyName()), '<=>', $model->getKey());
601+
$models = BaseCollection::wrap($model);
602+
603+
if ($models->isEmpty()) {
604+
throw new InvalidArgumentException('Collection given to whereNotMorphedTo method may not be empty.');
605+
}
606+
607+
return $this->whereNot(function ($query) use ($relation, $models) {
608+
$models->groupBy(fn ($model) => $model->getMorphClass())->each(function ($models) use ($query, $relation) {
609+
$query->orWhere(function ($query) use ($relation, $models) {
610+
$query->where($relation->qualifyColumn($relation->getMorphType()), '<=>', $models->first()->getMorphClass())
611+
->whereNotIn($relation->qualifyColumn($relation->getForeignKeyName()), $models->map->getKey());
612+
});
613+
});
594614
}, null, null, $boolean);
595615
}
596616

597617
/**
598618
* Add a morph-to relationship condition to the query with an "or where" clause.
599619
*
600620
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
601-
* @param \Illuminate\Database\Eloquent\Model|string|null $model
621+
* @param \Illuminate\Database\Eloquent\Model|iterable<int, \Illuminate\Database\Eloquent\Model>|string|null $model
602622
* @return $this
603623
*/
604624
public function orWhereMorphedTo($relation, $model)
@@ -610,7 +630,7 @@ public function orWhereMorphedTo($relation, $model)
610630
* Add a not morph-to relationship condition to the query with an "or where" clause.
611631
*
612632
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
613-
* @param \Illuminate\Database\Eloquent\Model|string $model
633+
* @param \Illuminate\Database\Eloquent\Model|iterable<int, \Illuminate\Database\Eloquent\Model>|string $model
614634
* @return $this
615635
*/
616636
public function orWhereNotMorphedTo($relation, $model)

tests/Database/DatabaseEloquentBuilderTest.php

+152-4
Original file line numberDiff line numberDiff line change
@@ -1774,10 +1774,47 @@ public function testWhereMorphedTo()
17741774

17751775
$builder = $model->whereMorphedTo('morph', $relatedModel);
17761776

1777-
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where ("eloquent_builder_test_model_parent_stubs"."morph_type" = ? and "eloquent_builder_test_model_parent_stubs"."morph_id" = ?)', $builder->toSql());
1777+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where (("eloquent_builder_test_model_parent_stubs"."morph_type" = ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?)))', $builder->toSql());
17781778
$this->assertEquals([$relatedModel->getMorphClass(), $relatedModel->getKey()], $builder->getBindings());
17791779
}
17801780

1781+
public function testWhereMorphedToCollection()
1782+
{
1783+
$model = new EloquentBuilderTestModelParentStub;
1784+
$this->mockConnectionForModel($model, '');
1785+
1786+
$firstRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1787+
$firstRelatedModel->id = 1;
1788+
1789+
$secondRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1790+
$secondRelatedModel->id = 2;
1791+
1792+
$builder = $model->whereMorphedTo('morph', new Collection([$firstRelatedModel, $secondRelatedModel]));
1793+
1794+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where (("eloquent_builder_test_model_parent_stubs"."morph_type" = ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?, ?)))', $builder->toSql());
1795+
$this->assertEquals([$firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $secondRelatedModel->getKey()], $builder->getBindings());
1796+
}
1797+
1798+
public function testWhereMorphedToCollectionWithDifferentModels()
1799+
{
1800+
$model = new EloquentBuilderTestModelParentStub;
1801+
$this->mockConnectionForModel($model, '');
1802+
1803+
$firstRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1804+
$firstRelatedModel->id = 1;
1805+
1806+
$secondRelatedModel = new EloquentBuilderTestModelFarRelatedStub;
1807+
$secondRelatedModel->id = 2;
1808+
1809+
$thirdRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1810+
$thirdRelatedModel->id = 3;
1811+
1812+
$builder = $model->whereMorphedTo('morph', [$firstRelatedModel, $secondRelatedModel, $thirdRelatedModel]);
1813+
1814+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where (("eloquent_builder_test_model_parent_stubs"."morph_type" = ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?, ?)) or ("eloquent_builder_test_model_parent_stubs"."morph_type" = ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?)))', $builder->toSql());
1815+
$this->assertEquals([$firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $thirdRelatedModel->getKey(), $secondRelatedModel->getMorphClass(), $secondRelatedModel->id], $builder->getBindings());
1816+
}
1817+
17811818
public function testWhereMorphedToNull()
17821819
{
17831820
$model = new EloquentBuilderTestModelParentStub;
@@ -1797,10 +1834,47 @@ public function testWhereNotMorphedTo()
17971834

17981835
$builder = $model->whereNotMorphedTo('morph', $relatedModel);
17991836

1800-
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not ("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" <=> ?)', $builder->toSql());
1837+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?)))', $builder->toSql());
18011838
$this->assertEquals([$relatedModel->getMorphClass(), $relatedModel->getKey()], $builder->getBindings());
18021839
}
18031840

1841+
public function testWhereNotMorphedToCollection()
1842+
{
1843+
$model = new EloquentBuilderTestModelParentStub;
1844+
$this->mockConnectionForModel($model, '');
1845+
1846+
$firstRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1847+
$firstRelatedModel->id = 1;
1848+
1849+
$secondRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1850+
$secondRelatedModel->id = 2;
1851+
1852+
$builder = $model->whereNotMorphedTo('morph', new Collection([$firstRelatedModel, $secondRelatedModel]));
1853+
1854+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?, ?)))', $builder->toSql());
1855+
$this->assertEquals([$firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $secondRelatedModel->getKey()], $builder->getBindings());
1856+
}
1857+
1858+
public function testWhereNotMorphedToCollectionWithDifferentModels()
1859+
{
1860+
$model = new EloquentBuilderTestModelParentStub;
1861+
$this->mockConnectionForModel($model, '');
1862+
1863+
$firstRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1864+
$firstRelatedModel->id = 1;
1865+
1866+
$secondRelatedModel = new EloquentBuilderTestModelFarRelatedStub;
1867+
$secondRelatedModel->id = 2;
1868+
1869+
$thirdRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1870+
$thirdRelatedModel->id = 3;
1871+
1872+
$builder = $model->whereNotMorphedTo('morph', [$firstRelatedModel, $secondRelatedModel, $thirdRelatedModel]);
1873+
1874+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?, ?)) or ("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?)))', $builder->toSql());
1875+
$this->assertEquals([$firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $thirdRelatedModel->getKey(), $secondRelatedModel->getMorphClass(), $secondRelatedModel->id], $builder->getBindings());
1876+
}
1877+
18041878
public function testOrWhereMorphedTo()
18051879
{
18061880
$model = new EloquentBuilderTestModelParentStub;
@@ -1811,10 +1885,47 @@ public function testOrWhereMorphedTo()
18111885

18121886
$builder = $model->where('bar', 'baz')->orWhereMorphedTo('morph', $relatedModel);
18131887

1814-
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or ("eloquent_builder_test_model_parent_stubs"."morph_type" = ? and "eloquent_builder_test_model_parent_stubs"."morph_id" = ?)', $builder->toSql());
1888+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or (("eloquent_builder_test_model_parent_stubs"."morph_type" = ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?)))', $builder->toSql());
18151889
$this->assertEquals(['baz', $relatedModel->getMorphClass(), $relatedModel->getKey()], $builder->getBindings());
18161890
}
18171891

1892+
public function testOrWhereMorphedToCollection()
1893+
{
1894+
$model = new EloquentBuilderTestModelParentStub;
1895+
$this->mockConnectionForModel($model, '');
1896+
1897+
$firstRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1898+
$firstRelatedModel->id = 1;
1899+
1900+
$secondRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1901+
$secondRelatedModel->id = 2;
1902+
1903+
$builder = $model->where('bar', 'baz')->orWhereMorphedTo('morph', new Collection([$firstRelatedModel, $secondRelatedModel]));
1904+
1905+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or (("eloquent_builder_test_model_parent_stubs"."morph_type" = ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?, ?)))', $builder->toSql());
1906+
$this->assertEquals(['baz', $firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $secondRelatedModel->getKey()], $builder->getBindings());
1907+
}
1908+
1909+
public function testOrWhereMorphedToCollectionWithDifferentModels()
1910+
{
1911+
$model = new EloquentBuilderTestModelParentStub;
1912+
$this->mockConnectionForModel($model, '');
1913+
1914+
$firstRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1915+
$firstRelatedModel->id = 1;
1916+
1917+
$secondRelatedModel = new EloquentBuilderTestModelFarRelatedStub;
1918+
$secondRelatedModel->id = 2;
1919+
1920+
$thirdRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1921+
$thirdRelatedModel->id = 3;
1922+
1923+
$builder = $model->where('bar', 'baz')->orWhereMorphedTo('morph', [$firstRelatedModel, $secondRelatedModel, $thirdRelatedModel]);
1924+
1925+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or (("eloquent_builder_test_model_parent_stubs"."morph_type" = ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?, ?)) or ("eloquent_builder_test_model_parent_stubs"."morph_type" = ? and "eloquent_builder_test_model_parent_stubs"."morph_id" in (?)))', $builder->toSql());
1926+
$this->assertEquals(['baz', $firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $thirdRelatedModel->getKey(), $secondRelatedModel->getMorphClass(), $secondRelatedModel->id], $builder->getBindings());
1927+
}
1928+
18181929
public function testOrWhereMorphedToNull()
18191930
{
18201931
$model = new EloquentBuilderTestModelParentStub;
@@ -1836,10 +1947,47 @@ public function testOrWhereNotMorphedTo()
18361947

18371948
$builder = $model->where('bar', 'baz')->orWhereNotMorphedTo('morph', $relatedModel);
18381949

1839-
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or not ("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" <=> ?)', $builder->toSql());
1950+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?)))', $builder->toSql());
18401951
$this->assertEquals(['baz', $relatedModel->getMorphClass(), $relatedModel->getKey()], $builder->getBindings());
18411952
}
18421953

1954+
public function testOrWhereNotMorphedToCollection()
1955+
{
1956+
$model = new EloquentBuilderTestModelParentStub;
1957+
$this->mockConnectionForModel($model, '');
1958+
1959+
$firstRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1960+
$firstRelatedModel->id = 1;
1961+
1962+
$secondRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1963+
$secondRelatedModel->id = 2;
1964+
1965+
$builder = $model->where('bar', 'baz')->orWhereNotMorphedTo('morph', new Collection([$firstRelatedModel, $secondRelatedModel]));
1966+
1967+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?, ?)))', $builder->toSql());
1968+
$this->assertEquals(['baz', $firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $secondRelatedModel->getKey()], $builder->getBindings());
1969+
}
1970+
1971+
public function testOrWhereNotMorphedToCollectionWithDifferentModels()
1972+
{
1973+
$model = new EloquentBuilderTestModelParentStub;
1974+
$this->mockConnectionForModel($model, '');
1975+
1976+
$firstRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1977+
$firstRelatedModel->id = 1;
1978+
1979+
$secondRelatedModel = new EloquentBuilderTestModelFarRelatedStub;
1980+
$secondRelatedModel->id = 2;
1981+
1982+
$thirdRelatedModel = new EloquentBuilderTestModelCloseRelatedStub;
1983+
$thirdRelatedModel->id = 3;
1984+
1985+
$builder = $model->where('bar', 'baz')->orWhereNotMorphedTo('morph', [$firstRelatedModel, $secondRelatedModel, $thirdRelatedModel]);
1986+
1987+
$this->assertSame('select * from "eloquent_builder_test_model_parent_stubs" where "bar" = ? or not (("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?, ?)) or ("eloquent_builder_test_model_parent_stubs"."morph_type" <=> ? and "eloquent_builder_test_model_parent_stubs"."morph_id" not in (?)))', $builder->toSql());
1988+
$this->assertEquals(['baz', $firstRelatedModel->getMorphClass(), $firstRelatedModel->getKey(), $thirdRelatedModel->getKey(), $secondRelatedModel->getMorphClass(), $secondRelatedModel->id], $builder->getBindings());
1989+
}
1990+
18431991
public function testWhereMorphedToClass()
18441992
{
18451993
$model = new EloquentBuilderTestModelParentStub;

0 commit comments

Comments
 (0)