Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.1] Added morph map #9891

Merged
merged 2 commits into from Sep 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 27 additions & 1 deletion src/Illuminate/Database/Eloquent/Model.php
Expand Up @@ -870,6 +870,8 @@ public function morphTo($name = null, $type = null, $id = null)
// as a belongs-to style relationship since morph-to extends that class and
// we will pass in the appropriate values so that it behaves as expected.
else {
$class = $this->getActualClassName($class);

$instance = new $class;

return new MorphTo(
Expand All @@ -878,6 +880,23 @@ public function morphTo($name = null, $type = null, $id = null)
}
}

/**
* Retrieve the fully qualified class name from a slug.
*
* @param string $class
* @return string
*/
protected function getActualClassName($class)
{
$morphMap = Relation::morphMap();

if (isset($morphMap[$class])) {
return $morphMap[$class];
}

return $class;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can replace everything in this method with:

return array_get(Relation::morphMap(), $class, $class);

}

/**
* Define a one-to-many relationship.
*
Expand Down Expand Up @@ -2054,7 +2073,14 @@ protected function getMorphs($name, $type, $id)
*/
public function getMorphClass()
{
return $this->morphClass ?: get_class($this);
$morphMap = Relation::morphMap();
$class = get_class($this);

if (! empty($morphMap)) {
return array_search($class, $morphMap, true);
}

return $this->morphClass ?: $class;
}

/**
Expand Down
4 changes: 3 additions & 1 deletion src/Illuminate/Database/Eloquent/Relations/MorphTo.php
Expand Up @@ -213,7 +213,9 @@ protected function gatherKeysByType($type)
*/
public function createModelByType($type)
{
return new $type;
$class = $this->parent->getActualClassName($type);

return new $class;
}

/**
Expand Down
23 changes: 23 additions & 0 deletions src/Illuminate/Database/Eloquent/Relations/Relation.php
Expand Up @@ -38,6 +38,13 @@ abstract class Relation
*/
protected static $constraints = true;

/**
* An array to map class names to their morph names in database.
*
* @var array
*/
protected static $morphMap = [];

/**
* Create a new relation instance.
*
Expand Down Expand Up @@ -272,6 +279,22 @@ public function wrap($value)
return $this->parent->newQueryWithoutScopes()->getQuery()->getGrammar()->wrap($value);
}

/**
* Set the morph map for polymorphic relations.
*
* @param array|null $map
* @param bool $merge
* @return array
*/
public static function morphMap(array $map = null, $merge = true)
{
if (is_array($map)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can reduce nesting by doing the reverse of this and early returning static::$morphMap.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GrahamCampbell Would that imply getting rid of the is_array check? Or do you merely mean a return inside the if statement to avoid the else?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I'm saying do ! is_array() first.

static::$morphMap = $merge ? array_merge(static::$morphMap, $map) : $map;
}

return static::$morphMap;
}

/**
* Handle dynamic method calls to the relationship.
*
Expand Down
79 changes: 79 additions & 0 deletions tests/Database/DatabaseEloquentIntegrationTest.php
Expand Up @@ -3,6 +3,7 @@
use Illuminate\Database\Connection;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Pagination\AbstractPaginator as Paginator;
use Illuminate\Database\Eloquent\Relations\Relation;

class DatabaseEloquentIntegrationTest extends PHPUnit_Framework_TestCase
{
Expand Down Expand Up @@ -76,6 +77,8 @@ public function tearDown()
$this->schema()->drop('friends');
$this->schema()->drop('posts');
$this->schema()->drop('photos');

Illuminate\Database\Eloquent\Relations\Relation::morphMap([], false);
}

/**
Expand Down Expand Up @@ -340,6 +343,82 @@ public function testBasicMorphManyRelationship()
$this->assertEquals('First Post', $photos[3]->imageable->name);
}

public function testMorphMapIsUsedForCreatingAndFetchingThroughRelation()
{
Illuminate\Database\Eloquent\Relations\Relation::morphMap([
'user' => 'EloquentTestUser',
'post' => 'EloquentTestPost',
]);

$user = EloquentTestUser::create(['email' => 'taylorotwell@gmail.com']);
$user->photos()->create(['name' => 'Avatar 1']);
$user->photos()->create(['name' => 'Avatar 2']);
$post = $user->posts()->create(['name' => 'First Post']);
$post->photos()->create(['name' => 'Hero 1']);
$post->photos()->create(['name' => 'Hero 2']);

$this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $user->photos);
$this->assertInstanceOf('EloquentTestPhoto', $user->photos[0]);
$this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $post->photos);
$this->assertInstanceOf('EloquentTestPhoto', $post->photos[0]);
$this->assertEquals(2, $user->photos->count());
$this->assertEquals(2, $post->photos->count());
$this->assertEquals('Avatar 1', $user->photos[0]->name);
$this->assertEquals('Avatar 2', $user->photos[1]->name);
$this->assertEquals('Hero 1', $post->photos[0]->name);
$this->assertEquals('Hero 2', $post->photos[1]->name);

$this->assertEquals('user', $user->photos[0]->imageable_type);
$this->assertEquals('user', $user->photos[1]->imageable_type);
$this->assertEquals('post', $post->photos[0]->imageable_type);
$this->assertEquals('post', $post->photos[1]->imageable_type);
}

public function testMorphMapIsUsedWhenFetchingParent()
{
Relation::morphMap([
'user' => 'EloquentTestUser',
'post' => 'EloquentTestPost',
]);

$user = EloquentTestUser::create(['email' => 'taylorotwell@gmail.com']);
$user->photos()->create(['name' => 'Avatar 1']);

$photo = EloquentTestPhoto::first();
$this->assertEquals('user', $photo->imageable_type);
$this->assertInstanceOf('EloquentTestUser', $photo->imageable);
}

public function testMorphMapIsMergedByDefault()
{
$map1 = [
'user' => 'EloquentTestUser',
];
$map2 = [
'post' => 'EloquentTestPost',
];

Relation::morphMap($map1);
Relation::morphMap($map2);

$this->assertEquals(array_merge($map1, $map2), Relation::morphMap());
}

public function testMorphMapOverwritesCurrentMap()
{
$map1 = [
'user' => 'EloquentTestUser',
];
$map2 = [
'post' => 'EloquentTestPost',
];

Relation::morphMap($map1, false);
$this->assertEquals($map1, Relation::morphMap());
Relation::morphMap($map2, false);
$this->assertEquals($map2, Relation::morphMap());
}

public function testEmptyMorphToRelationship()
{
$photo = EloquentTestPhoto::create(['name' => 'Avatar 1']);
Expand Down
31 changes: 31 additions & 0 deletions tests/Database/DatabaseEloquentMorphTest.php
Expand Up @@ -165,6 +165,18 @@ public function testUpdateOrCreateMethodCreatesNewMorphModel()
$this->assertTrue($relation->updateOrCreate(['foo'], ['bar']) instanceof Model);
}

public function testCreateFunctionOnNamespacedMorph()
{
$relation = $this->getNamespacedRelation('namespace');
$created = m::mock('Illuminate\Database\Eloquent\Model');
$created->shouldReceive('setAttribute')->once()->with('morph_id', 1);
$created->shouldReceive('setAttribute')->once()->with('morph_type', 'namespace');
$relation->getRelated()->shouldReceive('newInstance')->once()->with(['name' => 'taylor'])->andReturn($created);
$created->shouldReceive('save')->once()->andReturn(true);

$this->assertEquals($created, $relation->create(['name' => 'taylor']));
}

protected function getOneRelation()
{
$builder = m::mock('Illuminate\Database\Eloquent\Builder');
Expand Down Expand Up @@ -194,6 +206,25 @@ protected function getManyRelation()

return new MorphMany($builder, $parent, 'table.morph_type', 'table.morph_id', 'id');
}

protected function getNamespacedRelation($alias)
{
Illuminate\Database\Eloquent\Relations\Relation::morphMap([
$alias => 'Foo\Bar\EloquentModelNamespacedStub',
]);

$builder = m::mock('Illuminate\Database\Eloquent\Builder');
$builder->shouldReceive('whereNotNull')->once()->with('table.morph_id');
$builder->shouldReceive('where')->once()->with('table.morph_id', '=', 1);
$related = m::mock('Illuminate\Database\Eloquent\Model');
$builder->shouldReceive('getModel')->andReturn($related);
$parent = m::mock('Foo\Bar\EloquentModelNamespacedStub');
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
$parent->shouldReceive('getMorphClass')->andReturn($alias);
$builder->shouldReceive('where')->once()->with('table.morph_type', $alias);

return new MorphOne($builder, $parent, 'table.morph_type', 'table.morph_id', 'id');
}
}

class EloquentMorphResetModelStub extends Illuminate\Database\Eloquent\Model
Expand Down