Skip to content

Commit

Permalink
HasOne, HasMany, and morph relationships may now use any key on paren…
Browse files Browse the repository at this point in the history
…t model, not just primary key.
  • Loading branch information
taylorotwell committed Oct 31, 2013
1 parent c7d2740 commit a634f45
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 30 deletions.
27 changes: 19 additions & 8 deletions src/Illuminate/Database/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -549,15 +549,18 @@ public static function with($relations)
*
* @param string $related
* @param string $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function hasOne($related, $foreignKey = null)
public function hasOne($related, $foreignKey = null, $localKey = null)
{
$foreignKey = $foreignKey ?: $this->getForeignKey();

$instance = new $related;

return new HasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey);
$localKey = $localKey ?: $this->getKeyName();

return new HasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
}

/**
Expand All @@ -569,15 +572,17 @@ public function hasOne($related, $foreignKey = null)
* @param string $id
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function morphOne($related, $name, $type = null, $id = null)
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
{
$instance = new $related;

list($type, $id) = $this->getMorphs($name, $type, $id);

$table = $instance->getTable();

return new MorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id);
$localKey = $localKey ?: $this->getKeyName();

return new MorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
}

/**
Expand Down Expand Up @@ -649,15 +654,18 @@ public function morphTo($name = null, $type = null, $id = null)
*
* @param string $related
* @param string $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function hasMany($related, $foreignKey = null)
public function hasMany($related, $foreignKey = null, $localKey = null)
{
$foreignKey = $foreignKey ?: $this->getForeignKey();

$instance = new $related;

return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey);
$localKey = $localKey ?: $this->getKeyName();

return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
}

/**
Expand Down Expand Up @@ -687,9 +695,10 @@ public function hasManyThrough($related, $through, $firstKey = null, $secondKey
* @param string $name
* @param string $type
* @param string $id
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function morphMany($related, $name, $type = null, $id = null)
public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
{
$instance = new $related;

Expand All @@ -700,7 +709,9 @@ public function morphMany($related, $name, $type = null, $id = null)

$table = $instance->getTable();

return new MorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id);
$localKey = $localKey ?: $this->getKeyName();

return new MorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
}

/**
Expand Down
30 changes: 23 additions & 7 deletions src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ abstract class HasOneOrMany extends Relation {
*/
protected $foreignKey;

/**
* The local key of the parent model.
*
* @var string
*/
protected $localKey;

/**
* Create a new has many relationship instance.
*
Expand All @@ -21,8 +28,9 @@ abstract class HasOneOrMany extends Relation {
* @param string $foreignKey
* @return void
*/
public function __construct(Builder $query, Model $parent, $foreignKey)
public function __construct(Builder $query, Model $parent, $foreignKey, $localKey)
{
$this->localKey = $localKey;
$this->foreignKey = $foreignKey;

parent::__construct($query, $parent);
Expand All @@ -37,9 +45,7 @@ public function addConstraints()
{
if (static::$constraints)
{
$key = $this->parent->getKey();

$this->query->where($this->foreignKey, '=', $key);
$this->query->where($this->foreignKey, '=', $this->getParentKey());
}
}

Expand Down Expand Up @@ -98,7 +104,7 @@ protected function matchOneOrMany(array $models, Collection $results, $relation,
// matching very convenient and easy work. Then we'll just return them.
foreach ($models as $model)
{
$key = $model->getKey();
$key = $model->getAttribute($this->localKey);

if (isset($dictionary[$key]))
{
Expand Down Expand Up @@ -157,7 +163,7 @@ protected function buildDictionary(Collection $results)
*/
public function save(Model $model)
{
$model->setAttribute($this->getPlainForeignKey(), $this->parent->getKey());
$model->setAttribute($this->getPlainForeignKey(), $this->getParentKey());

return $model->save() ? $model : false;
}
Expand All @@ -184,7 +190,7 @@ public function saveMany(array $models)
public function create(array $attributes)
{
$foreign = array(
$this->getPlainForeignKey() => $this->parent->getKey()
$this->getPlainForeignKey() => $this->getParentKey(),
);

// Here we will set the raw attributes to avoid hitting the "fill" method so
Expand Down Expand Up @@ -255,4 +261,14 @@ public function getPlainForeignKey()
return $segments[count($segments) - 1];
}

/**
* Get the key value of the paren's local key.
*
* @return mixed
*/
protected function getParentKey()
{
return $this->parent->getAttribute($this->localKey);
}

}
9 changes: 5 additions & 4 deletions src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ abstract class MorphOneOrMany extends HasOneOrMany {
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $type
* @param string $id
* @param string $localKey
* @return void
*/
public function __construct(Builder $query, Model $parent, $type, $id)
public function __construct(Builder $query, Model $parent, $type, $id, $localKey)
{
$this->morphType = $type;

$this->morphClass = get_class($parent);

parent::__construct($query, $parent, $id);
parent::__construct($query, $parent, $id, $localKey);
}

/**
Expand Down Expand Up @@ -76,7 +77,7 @@ public function addEagerConstraints(array $models)
parent::addEagerConstraints($models);

$this->query->where($this->morphType, $this->morphClass);
}
}

/**
* Attach a model instance to the parent model.
Expand Down Expand Up @@ -120,7 +121,7 @@ public function create(array $attributes)
*/
protected function getForeignAttributesForCreate()
{
$foreign = array($this->getPlainForeignKey() => $this->parent->getKey());
$foreign = array($this->getPlainForeignKey() => $this->getParentKey());

$foreign[last(explode('.', $this->morphType))] = $this->morphClass;

Expand Down
3 changes: 2 additions & 1 deletion src/Illuminate/Foundation/changes.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
{"message": "Allow comma delimited list of queues to be passed to queue:listen / queue:work to implement queue priority.", "backport": null},
{"message": "When new bindings are added to container, old aliases bound to that key will now be dropped.", "backport": null},
{"message": "Added new 'resolvable' and 'isAlias' methods to the container.", "backport": null},
{"message": "BelongsTo relationships may now reference any key on parent model, not just primary key.", "backport": null}
{"message": "BelongsTo relationships may now reference any key on parent model, not just primary key.", "backport": null},
{"message": "HasOne, HasMany, and morph relationships may now use any key on parent model, not just primary key.", "backport": null}
],
"4.0.x": [
{"message": "Added implode method to query builder and Collection class.", "backport": null},
Expand Down
4 changes: 2 additions & 2 deletions tests/Database/DatabaseEloquentHasManyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ protected function getRelation()
$related = m::mock('Illuminate\Database\Eloquent\Model');
$builder->shouldReceive('getModel')->andReturn($related);
$parent = m::mock('Illuminate\Database\Eloquent\Model');
$parent->shouldReceive('getKey')->andReturn(1);
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
$parent->shouldReceive('getCreatedAtColumn')->andReturn('created_at');
$parent->shouldReceive('getUpdatedAtColumn')->andReturn('updated_at');
return new HasMany($builder, $parent, 'table.foreign_key');
return new HasMany($builder, $parent, 'table.foreign_key', 'id');
}

}
Expand Down
4 changes: 2 additions & 2 deletions tests/Database/DatabaseEloquentHasOneTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ protected function getRelation()
$related = m::mock('Illuminate\Database\Eloquent\Model');
$builder->shouldReceive('getModel')->andReturn($related);
$parent = m::mock('Illuminate\Database\Eloquent\Model');
$parent->shouldReceive('getKey')->andReturn(1);
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
$parent->shouldReceive('getCreatedAtColumn')->andReturn('created_at');
$parent->shouldReceive('getUpdatedAtColumn')->andReturn('updated_at');
return new HasOne($builder, $parent, 'table.foreign_key');
return new HasOne($builder, $parent, 'table.foreign_key', 'id');
}

}
Expand Down
8 changes: 4 additions & 4 deletions tests/Database/DatabaseEloquentMorphTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ protected function getOneRelation()
$related = m::mock('Illuminate\Database\Eloquent\Model');
$builder->shouldReceive('getModel')->andReturn($related);
$parent = m::mock('Illuminate\Database\Eloquent\Model');
$parent->shouldReceive('getKey')->andReturn(1);
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
$builder->shouldReceive('where')->once()->with('table.morph_type', get_class($parent));
return new MorphOne($builder, $parent, 'table.morph_type', 'table.morph_id');
return new MorphOne($builder, $parent, 'table.morph_type', 'table.morph_id', 'id');
}


Expand All @@ -88,9 +88,9 @@ protected function getManyRelation()
$related = m::mock('Illuminate\Database\Eloquent\Model');
$builder->shouldReceive('getModel')->andReturn($related);
$parent = m::mock('Illuminate\Database\Eloquent\Model');
$parent->shouldReceive('getKey')->andReturn(1);
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
$builder->shouldReceive('where')->once()->with('table.morph_type', get_class($parent));
return new MorphMany($builder, $parent, 'table.morph_type', 'table.morph_id');
return new MorphMany($builder, $parent, 'table.morph_type', 'table.morph_id', 'id');
}

}
Expand Down
4 changes: 2 additions & 2 deletions tests/Database/DatabaseEloquentRelationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ public function testTouchMethodUpdatesRelatedTimestamps()
{
$builder = m::mock('Illuminate\Database\Eloquent\Builder');
$parent = m::mock('Illuminate\Database\Eloquent\Model');
$parent->shouldReceive('getKey')->andReturn(1);
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
$builder->shouldReceive('getModel')->andReturn($related = m::mock('StdClass'));
$builder->shouldReceive('where');
$relation = new HasOne($builder, $parent, 'foreign_key');
$relation = new HasOne($builder, $parent, 'foreign_key', 'id');
$related->shouldReceive('getTable')->andReturn('table');
$related->shouldReceive('getUpdatedAtColumn')->andReturn('updated_at');
$related->shouldReceive('freshTimestampString')->andReturn(new DateTime);
Expand Down

0 comments on commit a634f45

Please sign in to comment.