diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php index d5599da4378c..a9b2916883cb 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php @@ -5,6 +5,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Str; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -59,7 +60,21 @@ public function hasOne($related, $foreignKey = null, $localKey = null) $localKey = $localKey ?: $this->getKeyName(); - return new HasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey); + return $this->newHasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey); + } + + /** + * Instantiate a new HasOne relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $foreignKey + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey) + { + return new HasOne($query, $parent, $foreignKey, $localKey); } /** @@ -82,7 +97,22 @@ public function morphOne($related, $name, $type = null, $id = null, $localKey = $localKey = $localKey ?: $this->getKeyName(); - return new MorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey); + return $this->newMorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey); + } + + /** + * Instantiate a new MorphOne relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $type + * @param string $id + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\MorphOne + */ + protected function newMorphOne(Builder $query, Model $parent, $type, $id, $localKey) + { + return new MorphOne($query, $parent, $type, $id, $localKey); } /** @@ -117,11 +147,28 @@ public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relat // actually be responsible for retrieving and hydrating every relations. $ownerKey = $ownerKey ?: $instance->getKeyName(); - return new BelongsTo( + return $this->newBelongsTo( $instance->newQuery(), $this, $foreignKey, $ownerKey, $relation ); } + /** + * Instantiate a new BelongsTo relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $child + * @param string $foreignKey + * @param string $ownerKey + * @param string $relation + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation) + { + return new BelongsTo( + $query, $child, $foreignKey, $ownerKey, $relation + ); + } + /** * Define a polymorphic, inverse one-to-one or many relationship. * @@ -159,7 +206,7 @@ public function morphTo($name = null, $type = null, $id = null) */ protected function morphEagerTo($name, $type, $id) { - return new MorphTo( + return $this->newMorphTo( $this->newQuery()->setEagerLoads([]), $this, $id, null, $type, $name ); } @@ -179,11 +226,27 @@ protected function morphInstanceTo($target, $name, $type, $id) static::getActualClassNameForMorph($target) ); - return new MorphTo( + return $this->newMorphTo( $instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name ); } + /** + * Instantiate a new MorphTo relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $foreignKey + * @param string $ownerKey + * @param string $type + * @param string $relation + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation) + { + return new MorphTo($query, $parent, $foreignKey, $ownerKey, $type, $relation); + } + /** * Retrieve the actual class name for a given morph class. * @@ -223,11 +286,25 @@ public function hasMany($related, $foreignKey = null, $localKey = null) $localKey = $localKey ?: $this->getKeyName(); - return new HasMany( + return $this->newHasMany( $instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey ); } + /** + * Instantiate a new HasMany relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $foreignKey + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey) + { + return new HasMany($query, $parent, $foreignKey, $localKey); + } + /** * Define a has-many-through relationship. * @@ -253,7 +330,24 @@ public function hasManyThrough($related, $through, $firstKey = null, $secondKey $instance = $this->newRelatedInstance($related); - return new HasManyThrough($instance->newQuery(), $this, $through, $firstKey, $secondKey, $localKey, $secondLocalKey); + return $this->newHasManyThrough($instance->newQuery(), $this, $through, $firstKey, $secondKey, $localKey, $secondLocalKey); + } + + /** + * Instantiate a new HasManyThrough relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $farParent + * @param \Illuminate\Database\Eloquent\Model $throughParent + * @param string $firstKey + * @param string $secondKey + * @param string $localKey + * @param string $secondLocalKey + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + protected function newHasManyThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) + { + return new HasManyThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey); } /** @@ -279,7 +373,22 @@ public function morphMany($related, $name, $type = null, $id = null, $localKey = $localKey = $localKey ?: $this->getKeyName(); - return new MorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey); + return $this->newMorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey); + } + + /** + * Instantiate a new MorphMany relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $type + * @param string $id + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey) + { + return new MorphMany($query, $parent, $type, $id, $localKey); } /** @@ -320,13 +429,32 @@ public function belongsToMany($related, $table = null, $foreignPivotKey = null, $table = $this->joiningTable($related); } - return new BelongsToMany( + return $this->newBelongsToMany( $instance->newQuery(), $this, $table, $foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(), $relatedKey ?: $instance->getKeyName(), $relation ); } + /** + * Instantiate a new BelongsToMany relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $table + * @param string $foreignPivotKey + * @param string $relatedPivotKey + * @param string $parentKey + * @param string $relatedKey + * @param string $relationName + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + protected function newBelongsToMany(Builder $query, Model $parent, $table, $foreignPivotKey, + $relatedPivotKey, $parentKey, $relatedKey, $relationName = null) + { + return new BelongsToMany($query, $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName); + } + /** * Define a polymorphic many-to-many relationship. * @@ -360,13 +488,35 @@ public function morphToMany($related, $name, $table = null, $foreignPivotKey = n // appropriate query constraints then entirely manages the hydrations. $table = $table ?: Str::plural($name); - return new MorphToMany( + return $this->newMorphToMany( $instance->newQuery(), $this, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(), $relatedKey ?: $instance->getKeyName(), $caller, $inverse ); } + /** + * Instantiate a new HasManyThrough relationship. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Model $parent + * @param string $name + * @param string $table + * @param string $foreignPivotKey + * @param string $relatedPivotKey + * @param string $parentKey + * @param string $relatedKey + * @param string $relationName + * @param bool $inverse + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + protected function newMorphToMany(Builder $query, Model $parent, $name, $table, $foreignPivotKey, + $relatedPivotKey, $parentKey, $relatedKey, $relationName = null, $inverse = false) + { + return new MorphToMany($query, $parent, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, + $relationName, $inverse); + } + /** * Define a polymorphic, inverse many-to-many relationship. * diff --git a/tests/Integration/Database/EloquentRelationshipsTest.php b/tests/Integration/Database/EloquentRelationshipsTest.php new file mode 100644 index 000000000000..e841995c3422 --- /dev/null +++ b/tests/Integration/Database/EloquentRelationshipsTest.php @@ -0,0 +1,200 @@ +assertInstanceOf(HasOne::class, $post->attachment()); + $this->assertInstanceOf(BelongsTo::class, $post->author()); + $this->assertInstanceOf(HasMany::class, $post->comments()); + $this->assertInstanceOf(MorphOne::class, $post->owner()); + $this->assertInstanceOf(MorphMany::class, $post->likes()); + $this->assertInstanceOf(BelongsToMany::class, $post->viewers()); + $this->assertInstanceOf(HasManyThrough::class, $post->lovers()); + $this->assertInstanceOf(MorphToMany::class, $post->tags()); + $this->assertInstanceOf(MorphTo::class, $post->postable()); + } + + /** + * @test + * @group f + */ + public function overridden_relationships() + { + $post = new CustomPost; + + $this->assertInstanceOf(CustomHasOne::class, $post->attachment()); + $this->assertInstanceOf(CustomBelongsTo::class, $post->author()); + $this->assertInstanceOf(CustomHasMany::class, $post->comments()); + $this->assertInstanceOf(CustomMorphOne::class, $post->owner()); + $this->assertInstanceOf(CustomMorphMany::class, $post->likes()); + $this->assertInstanceOf(CustomBelongsToMany::class, $post->viewers()); + $this->assertInstanceOf(CustomHasManyThrough::class, $post->lovers()); + $this->assertInstanceOf(CustomMorphToMany::class, $post->tags()); + $this->assertInstanceOf(CustomMorphTo::class, $post->postable()); + } +} + +class FakeRelationship extends Model +{ +} + +class Post extends Model +{ + public function attachment() + { + return $this->hasOne(FakeRelationship::class); + } + + public function author() + { + return $this->belongsTo(FakeRelationship::class); + } + + public function comments() + { + return $this->hasMany(FakeRelationship::class); + } + + public function likes() + { + return $this->morphMany(FakeRelationship::class, 'actionable'); + } + + public function owner() + { + return $this->morphOne(FakeRelationship::class, 'property'); + } + + public function viewers() + { + return $this->belongsToMany(FakeRelationship::class); + } + + public function lovers() + { + return $this->hasManyThrough(FakeRelationship::class, FakeRelationship::class); + } + + public function tags() + { + return $this->morphToMany(FakeRelationship::class, 'taggable'); + } + + public function postable() + { + return $this->morphTo(); + } +} + +class CustomPost extends Post +{ + protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation) + { + return new CustomBelongsTo($query, $child, $foreignKey, $ownerKey, $relation); + } + + protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey) + { + return new CustomHasMany($query, $parent, $foreignKey, $localKey); + } + + protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey) + { + return new CustomHasOne($query, $parent, $foreignKey, $localKey); + } + + protected function newMorphOne(Builder $query, Model $parent, $type, $id, $localKey) + { + return new CustomMorphOne($query, $parent, $type, $id, $localKey); + } + + protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey) + { + return new CustomMorphMany($query, $parent, $type, $id, $localKey); + } + + protected function newBelongsToMany(Builder $query, Model $parent, $table, $foreignPivotKey, $relatedPivotKey, + $parentKey, $relatedKey, $relationName = null + ) { + return new CustomBelongsToMany($query, $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName); + } + + protected function newHasManyThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, + $secondKey, $localKey, $secondLocalKey + ) { + return new CustomHasManyThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey); + } + + protected function newMorphToMany(Builder $query, Model $parent, $name, $table, $foreignPivotKey, + $relatedPivotKey, $parentKey, $relatedKey, $relationName = null, $inverse = false) + { + return new CustomMorphToMany($query, $parent, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, + $relationName, $inverse); + } + + protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation) + { + return new CustomMorphTo($query, $parent, $foreignKey, $ownerKey, $type, $relation); + } +} + +class CustomHasOne extends HasOne +{ +} + +class CustomBelongsTo extends BelongsTo +{ +} + +class CustomHasMany extends HasMany +{ +} + +class CustomMorphOne extends MorphOne +{ +} + +class CustomMorphMany extends MorphMany +{ +} + +class CustomBelongsToMany extends BelongsToMany +{ +} + +class CustomHasManyThrough extends HasManyThrough +{ +} + +class CustomMorphToMany extends MorphToMany +{ +} + +class CustomMorphTo extends MorphTo +{ +}