Skip to content

Commit a634f45

Browse files
committed
HasOne, HasMany, and morph relationships may now use any key on parent model, not just primary key.
1 parent c7d2740 commit a634f45

File tree

8 files changed

+59
-30
lines changed

8 files changed

+59
-30
lines changed

src/Illuminate/Database/Eloquent/Model.php

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -549,15 +549,18 @@ public static function with($relations)
549549
*
550550
* @param string $related
551551
* @param string $foreignKey
552+
* @param string $localKey
552553
* @return \Illuminate\Database\Eloquent\Relations\HasOne
553554
*/
554-
public function hasOne($related, $foreignKey = null)
555+
public function hasOne($related, $foreignKey = null, $localKey = null)
555556
{
556557
$foreignKey = $foreignKey ?: $this->getForeignKey();
557558

558559
$instance = new $related;
559560

560-
return new HasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey);
561+
$localKey = $localKey ?: $this->getKeyName();
562+
563+
return new HasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
561564
}
562565

563566
/**
@@ -569,15 +572,17 @@ public function hasOne($related, $foreignKey = null)
569572
* @param string $id
570573
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
571574
*/
572-
public function morphOne($related, $name, $type = null, $id = null)
575+
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
573576
{
574577
$instance = new $related;
575578

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

578581
$table = $instance->getTable();
579582

580-
return new MorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id);
583+
$localKey = $localKey ?: $this->getKeyName();
584+
585+
return new MorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
581586
}
582587

583588
/**
@@ -649,15 +654,18 @@ public function morphTo($name = null, $type = null, $id = null)
649654
*
650655
* @param string $related
651656
* @param string $foreignKey
657+
* @param string $localKey
652658
* @return \Illuminate\Database\Eloquent\Relations\HasMany
653659
*/
654-
public function hasMany($related, $foreignKey = null)
660+
public function hasMany($related, $foreignKey = null, $localKey = null)
655661
{
656662
$foreignKey = $foreignKey ?: $this->getForeignKey();
657663

658664
$instance = new $related;
659665

660-
return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey);
666+
$localKey = $localKey ?: $this->getKeyName();
667+
668+
return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
661669
}
662670

663671
/**
@@ -687,9 +695,10 @@ public function hasManyThrough($related, $through, $firstKey = null, $secondKey
687695
* @param string $name
688696
* @param string $type
689697
* @param string $id
698+
* @param string $localKey
690699
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
691700
*/
692-
public function morphMany($related, $name, $type = null, $id = null)
701+
public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
693702
{
694703
$instance = new $related;
695704

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

701710
$table = $instance->getTable();
702711

703-
return new MorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id);
712+
$localKey = $localKey ?: $this->getKeyName();
713+
714+
return new MorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
704715
}
705716

706717
/**

src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ abstract class HasOneOrMany extends Relation {
1313
*/
1414
protected $foreignKey;
1515

16+
/**
17+
* The local key of the parent model.
18+
*
19+
* @var string
20+
*/
21+
protected $localKey;
22+
1623
/**
1724
* Create a new has many relationship instance.
1825
*
@@ -21,8 +28,9 @@ abstract class HasOneOrMany extends Relation {
2128
* @param string $foreignKey
2229
* @return void
2330
*/
24-
public function __construct(Builder $query, Model $parent, $foreignKey)
31+
public function __construct(Builder $query, Model $parent, $foreignKey, $localKey)
2532
{
33+
$this->localKey = $localKey;
2634
$this->foreignKey = $foreignKey;
2735

2836
parent::__construct($query, $parent);
@@ -37,9 +45,7 @@ public function addConstraints()
3745
{
3846
if (static::$constraints)
3947
{
40-
$key = $this->parent->getKey();
41-
42-
$this->query->where($this->foreignKey, '=', $key);
48+
$this->query->where($this->foreignKey, '=', $this->getParentKey());
4349
}
4450
}
4551

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

103109
if (isset($dictionary[$key]))
104110
{
@@ -157,7 +163,7 @@ protected function buildDictionary(Collection $results)
157163
*/
158164
public function save(Model $model)
159165
{
160-
$model->setAttribute($this->getPlainForeignKey(), $this->parent->getKey());
166+
$model->setAttribute($this->getPlainForeignKey(), $this->getParentKey());
161167

162168
return $model->save() ? $model : false;
163169
}
@@ -184,7 +190,7 @@ public function saveMany(array $models)
184190
public function create(array $attributes)
185191
{
186192
$foreign = array(
187-
$this->getPlainForeignKey() => $this->parent->getKey()
193+
$this->getPlainForeignKey() => $this->getParentKey(),
188194
);
189195

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

264+
/**
265+
* Get the key value of the paren's local key.
266+
*
267+
* @return mixed
268+
*/
269+
protected function getParentKey()
270+
{
271+
return $this->parent->getAttribute($this->localKey);
272+
}
273+
258274
}

src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,16 @@ abstract class MorphOneOrMany extends HasOneOrMany {
2626
* @param \Illuminate\Database\Eloquent\Model $parent
2727
* @param string $type
2828
* @param string $id
29+
* @param string $localKey
2930
* @return void
3031
*/
31-
public function __construct(Builder $query, Model $parent, $type, $id)
32+
public function __construct(Builder $query, Model $parent, $type, $id, $localKey)
3233
{
3334
$this->morphType = $type;
3435

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

37-
parent::__construct($query, $parent, $id);
38+
parent::__construct($query, $parent, $id, $localKey);
3839
}
3940

4041
/**
@@ -76,7 +77,7 @@ public function addEagerConstraints(array $models)
7677
parent::addEagerConstraints($models);
7778

7879
$this->query->where($this->morphType, $this->morphClass);
79-
}
80+
}
8081

8182
/**
8283
* Attach a model instance to the parent model.
@@ -120,7 +121,7 @@ public function create(array $attributes)
120121
*/
121122
protected function getForeignAttributesForCreate()
122123
{
123-
$foreign = array($this->getPlainForeignKey() => $this->parent->getKey());
124+
$foreign = array($this->getPlainForeignKey() => $this->getParentKey());
124125

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

src/Illuminate/Foundation/changes.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
{"message": "Allow comma delimited list of queues to be passed to queue:listen / queue:work to implement queue priority.", "backport": null},
3838
{"message": "When new bindings are added to container, old aliases bound to that key will now be dropped.", "backport": null},
3939
{"message": "Added new 'resolvable' and 'isAlias' methods to the container.", "backport": null},
40-
{"message": "BelongsTo relationships may now reference any key on parent model, not just primary key.", "backport": null}
40+
{"message": "BelongsTo relationships may now reference any key on parent model, not just primary key.", "backport": null},
41+
{"message": "HasOne, HasMany, and morph relationships may now use any key on parent model, not just primary key.", "backport": null}
4142
],
4243
"4.0.x": [
4344
{"message": "Added implode method to query builder and Collection class.", "backport": null},

tests/Database/DatabaseEloquentHasManyTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ protected function getRelation()
9696
$related = m::mock('Illuminate\Database\Eloquent\Model');
9797
$builder->shouldReceive('getModel')->andReturn($related);
9898
$parent = m::mock('Illuminate\Database\Eloquent\Model');
99-
$parent->shouldReceive('getKey')->andReturn(1);
99+
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
100100
$parent->shouldReceive('getCreatedAtColumn')->andReturn('created_at');
101101
$parent->shouldReceive('getUpdatedAtColumn')->andReturn('updated_at');
102-
return new HasMany($builder, $parent, 'table.foreign_key');
102+
return new HasMany($builder, $parent, 'table.foreign_key', 'id');
103103
}
104104

105105
}

tests/Database/DatabaseEloquentHasOneTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,10 @@ protected function getRelation()
117117
$related = m::mock('Illuminate\Database\Eloquent\Model');
118118
$builder->shouldReceive('getModel')->andReturn($related);
119119
$parent = m::mock('Illuminate\Database\Eloquent\Model');
120-
$parent->shouldReceive('getKey')->andReturn(1);
120+
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
121121
$parent->shouldReceive('getCreatedAtColumn')->andReturn('created_at');
122122
$parent->shouldReceive('getUpdatedAtColumn')->andReturn('updated_at');
123-
return new HasOne($builder, $parent, 'table.foreign_key');
123+
return new HasOne($builder, $parent, 'table.foreign_key', 'id');
124124
}
125125

126126
}

tests/Database/DatabaseEloquentMorphTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ protected function getOneRelation()
7575
$related = m::mock('Illuminate\Database\Eloquent\Model');
7676
$builder->shouldReceive('getModel')->andReturn($related);
7777
$parent = m::mock('Illuminate\Database\Eloquent\Model');
78-
$parent->shouldReceive('getKey')->andReturn(1);
78+
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
7979
$builder->shouldReceive('where')->once()->with('table.morph_type', get_class($parent));
80-
return new MorphOne($builder, $parent, 'table.morph_type', 'table.morph_id');
80+
return new MorphOne($builder, $parent, 'table.morph_type', 'table.morph_id', 'id');
8181
}
8282

8383

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

9696
}

tests/Database/DatabaseEloquentRelationTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ public function testTouchMethodUpdatesRelatedTimestamps()
1515
{
1616
$builder = m::mock('Illuminate\Database\Eloquent\Builder');
1717
$parent = m::mock('Illuminate\Database\Eloquent\Model');
18-
$parent->shouldReceive('getKey')->andReturn(1);
18+
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
1919
$builder->shouldReceive('getModel')->andReturn($related = m::mock('StdClass'));
2020
$builder->shouldReceive('where');
21-
$relation = new HasOne($builder, $parent, 'foreign_key');
21+
$relation = new HasOne($builder, $parent, 'foreign_key', 'id');
2222
$related->shouldReceive('getTable')->andReturn('table');
2323
$related->shouldReceive('getUpdatedAtColumn')->andReturn('updated_at');
2424
$related->shouldReceive('freshTimestampString')->andReturn(new DateTime);

0 commit comments

Comments
 (0)