Skip to content

Commit 249a6ce

Browse files
committed
Fix MorphTo bug
1 parent e08969e commit 249a6ce

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-0
lines changed

src/Jenssegers/Eloquent/Model.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Database\Eloquent\Relations\Relation;
88
use Jenssegers\Mongodb\Relations\BelongsTo;
99
use Jenssegers\Mongodb\Relations\BelongsToMany;
10+
use Jenssegers\Mongodb\Relations\MorphTo;
1011
use Jenssegers\Mongodb\Query\Builder as QueryBuilder;
1112

1213
abstract class Model extends \Illuminate\Database\Eloquent\Model {
@@ -169,6 +170,51 @@ public function belongsTo($related, $foreignKey = null, $otherKey = null, $relat
169170
return new BelongsTo($query, $this, $foreignKey, $otherKey, $relation);
170171
}
171172

173+
/**
174+
* Define a polymorphic, inverse one-to-one or many relationship.
175+
*
176+
* @param string $name
177+
* @param string $type
178+
* @param string $id
179+
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
180+
*/
181+
public function morphTo($name = null, $type = null, $id = null)
182+
{
183+
// If no name is provided, we will use the backtrace to get the function name
184+
// since that is most likely the name of the polymorphic interface. We can
185+
// use that to get both the class and foreign key that will be utilized.
186+
if (is_null($name))
187+
{
188+
list(, $caller) = debug_backtrace(false);
189+
190+
$name = snake_case($caller['function']);
191+
}
192+
193+
list($type, $id) = $this->getMorphs($name, $type, $id);
194+
195+
// If the type value is null it is probably safe to assume we're eager loading
196+
// the relationship. When that is the case we will pass in a dummy query as
197+
// there are multiple types in the morph and we can't use single queries.
198+
if (is_null($class = $this->$type))
199+
{
200+
return new MorphTo(
201+
$this->newQuery(), $this, $id, null, $type, $name
202+
);
203+
}
204+
205+
// If we are not eager loading the relatinship, we will essentially treat this
206+
// as a belongs-to style relationship since morph-to extends that class and
207+
// we will pass in the appropriate values so that it behaves as expected.
208+
else
209+
{
210+
$instance = new $class;
211+
212+
return new MorphTo(
213+
with($instance)->newQuery(), $this, $id, $instance->getKeyName(), $type, $name
214+
);
215+
}
216+
}
217+
172218
/**
173219
* Define a many-to-many relationship.
174220
*
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<?php namespace Jenssegers\Mongodb\Relations;
2+
3+
use Illuminate\Database\Eloquent\Model;
4+
use Illuminate\Database\Eloquent\Builder;
5+
use Illuminate\Database\Query\Expression;
6+
use Illuminate\Database\Eloquent\Collection;
7+
use Illuminate\Support\Collection as BaseCollection;
8+
9+
class MorphTo extends BelongsTo {
10+
11+
/**
12+
* The type of the polymorphic relation.
13+
*
14+
* @var string
15+
*/
16+
protected $morphType;
17+
18+
/**
19+
* The models whose relations are being eager loaded.
20+
*
21+
* @var \Illuminate\Database\Eloquent\Collection
22+
*/
23+
protected $models;
24+
25+
/**
26+
* All of the models keyed by ID.
27+
*
28+
* @var array
29+
*/
30+
protected $dictionary = array();
31+
32+
/**
33+
* Create a new belongs to relationship instance.
34+
*
35+
* @param \Illuminate\Database\Eloquent\Builder $query
36+
* @param \Illuminate\Database\Eloquent\Model $parent
37+
* @param string $foreignKey
38+
* @param string $otherKey
39+
* @param string $type
40+
* @param string $relation
41+
* @return void
42+
*/
43+
public function __construct(Builder $query, Model $parent, $foreignKey, $otherKey, $type, $relation)
44+
{
45+
$this->morphType = $type;
46+
47+
parent::__construct($query, $parent, $foreignKey, $otherKey, $relation);
48+
}
49+
50+
/**
51+
* Set the constraints for an eager load of the relation.
52+
*
53+
* @param array $models
54+
* @return void
55+
*/
56+
public function addEagerConstraints(array $models)
57+
{
58+
$this->buildDictionary($this->models = Collection::make($models));
59+
}
60+
61+
/**
62+
* Buiild a dictionary with the models.
63+
*
64+
* @param \Illuminate\Database\Eloquent\Models $models
65+
* @return void
66+
*/
67+
protected function buildDictionary(Collection $models)
68+
{
69+
foreach ($models as $model)
70+
{
71+
if ($model->{$this->morphType})
72+
{
73+
$this->dictionary[$model->{$this->morphType}][$model->{$this->foreignKey}][] = $model;
74+
}
75+
}
76+
}
77+
78+
/**
79+
* Match the eagerly loaded results to their parents.
80+
*
81+
* @param array $models
82+
* @param \Illuminate\Database\Eloquent\Collection $results
83+
* @param string $relation
84+
* @return array
85+
*/
86+
public function match(array $models, Collection $results, $relation)
87+
{
88+
return $models;
89+
}
90+
91+
/**
92+
* Get the results of the relationship.
93+
*
94+
* Called via eager load method of Eloquent query builder.
95+
*
96+
* @return mixed
97+
*/
98+
public function getEager()
99+
{
100+
foreach (array_keys($this->dictionary) as $type)
101+
{
102+
$this->matchToMorphParents($type, $this->getResultsByType($type));
103+
}
104+
105+
return $this->models;
106+
}
107+
108+
/**
109+
* Match the results for a given type to their parents.
110+
*
111+
* @param string $type
112+
* @param \Illuminate\Database\Eloquent\Collection $results
113+
* @return void
114+
*/
115+
protected function matchToMorphParents($type, Collection $results)
116+
{
117+
foreach ($results as $result)
118+
{
119+
if (isset($this->dictionary[$type][$result->getKey()]))
120+
{
121+
foreach ($this->dictionary[$type][$result->getKey()] as $model)
122+
{
123+
$model->setRelation($this->relation, $result);
124+
}
125+
}
126+
}
127+
}
128+
129+
/**
130+
* Get all of the relation results for a type.
131+
*
132+
* @param string $type
133+
* @return \Illuminate\Database\Eloquent\Collection
134+
*/
135+
protected function getResultsByType($type)
136+
{
137+
$instance = $this->createModelByType($type);
138+
139+
$key = $instance->getKeyName();
140+
141+
return $instance->whereIn($key, $this->gatherKeysByType($type)->all())->get();
142+
}
143+
144+
/**
145+
* Gather all of the foreign keys for a given type.
146+
*
147+
* @param string $type
148+
* @return array
149+
*/
150+
protected function gatherKeysByType($type)
151+
{
152+
$foreign = $this->foreignKey;
153+
154+
return BaseCollection::make($this->dictionary[$type])->map(function($models) use ($foreign)
155+
{
156+
return head($models)->{$foreign};
157+
158+
})->unique();
159+
}
160+
161+
/**
162+
* Create a new model instance by type.
163+
*
164+
* @param string $type
165+
* @return \Illuminate\Database\Eloquent\Model
166+
*/
167+
public function createModelByType($type)
168+
{
169+
return new $type;
170+
}
171+
172+
/**
173+
* Get the dictionary used by the relationship.
174+
*
175+
* @return array
176+
*/
177+
public function getDictionary()
178+
{
179+
return $this->dictionary;
180+
}
181+
182+
}

tests/RelationsTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,17 @@ public function testMorph()
274274
$this->assertEquals(1, $user->photos->count());
275275
$this->assertEquals($photo->id, $user->photos->first()->id);
276276

277+
$user = User::find($user->_id);
278+
$this->assertEquals(1, $user->photos->count());
279+
$this->assertEquals($photo->id, $user->photos->first()->id);
280+
277281
$photo = Photo::create(array('url' => 'http://graph.facebook.com/john.doe/picture'));
278282
$client->photos()->save($photo);
283+
284+
$this->assertEquals(1, $client->photos->count());
285+
$this->assertEquals($photo->id, $client->photos->first()->id);
286+
287+
$client = Client::find($client->_id);
279288
$this->assertEquals(1, $client->photos->count());
280289
$this->assertEquals($photo->id, $client->photos->first()->id);
281290

0 commit comments

Comments
 (0)