Laravel Eloquent and Namespacing Issues

eloquent, laravel, laravel-4, mysql, php

Solution

public function linkable()
{
    return $this->morphTo(null, App::make($this->linkable_type));
}

This will not work in any case, because `morphTo()` in Illuminate\Database\Eloquent\Model expects

- The name of the polymorphic relationship ('linkable')

- The type of the object to be morphed ('Page', not an instance of `Page`)

- The id of the object to be morphed

If they are not provided, Laravel is smart enough to guess them and then accordingly return a `Illuminate\Database\Eloquent\MorphTo` object.

Also, `$this->linkable_type` and `$this->linkable_id` should actually not be `null` in that context.

Let's have a quick look at the relevant part of the `morphTo()` function:

$instance = new $class;

return new MorphTo(
    $instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name
);

Note: This is the code from version 4.2.6, the code linked above seems to be from a later version and is slightly different and the function returns a `BelongsTo` instead of a `MorphTo` object.

The problem is specifically the `$instance = new $class;` - the class is simply instanciated and not resolved. But you can just grab that part of the magic and handle it yourself:

public function linkable()
{
    $instance = App::make($this->linkable_type);

    $id = 'linkable_id';
    $type = 'linkable_type';
    $name = 'linkable';

    return new MorphTo(
        $instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name
    );
}

This should actually work (haven't tested it), but I'm not sure about any side effects it might cause in some edge cases.

Or maybe you could also just override the whole function in your `MenuItem`-model and just adjust the relevant part:

public function morphTo($name = null, $type = null, $id = null)
{
    if (is_null($name))
    {
        list(, $caller) = debug_backtrace(false);

        $name = snake_case($caller['function']);
    }

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

    //eager loading
    if (is_null($class = $this->$type))
    {
        return new MorphTo(
            $this->newQuery(), $this, $id, null, $type, $name
        );
    }

    // normal lazy loading
    else
    {
        // this is the changed part
        $instance = \App::make($class); // new $class;

        return new MorphTo(
            $instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name
        );
    }
}

Note: This works well for lazy-loading, but does not work for eager-loading. An issue has been raised seeking a solution for eager-loading here.

The relationship would then be as usual:

public function linkable()
{
    return $this->morphTo();
}

Problem

I am building a CMS package for Laravel. All of my models in this package are bound to, and resolved from, the IoC container so that they can be easily overwritten in any individual deployment of the package. For non-polymorphic relationships, this has worked a charm. For instance, a Page has many PageModules, so its relationship changed from: ``` // \Angel\Core\Page public function modules() { return $this->hasMany('PageModule'); } ``` to: ``` // \Angel\Core\Page public function modules() { return $this->hasMany(App::make('PageModule')); } ``` But I haven't been able to figure out how to do the same thing with polymorphic relationships. For instance, Menus contain MenuItems, and each MenuItem can be tied to a single other model, such as a Page or BlogPost. To accomplish this the Laravel way, I've added the following relationship to MenuItem: ``` // \Angel\Core\MenuItem public function linkable() { return $this->morphTo(); } ``` And this relationship to LinkableModel, which all models such as Page and BlogPost extend: ``` // \Angel\Core\LinkableModel public function menuItem() { return $this->morphOne(App::make('MenuItem'), 'linkable'); } ``` And the `menus_items` table (which MenuItems use) has these rows: ``` linkable_type | linkable_id -------------------|-------------- \Angel\Core\Page | 11 \Angel\Core\Page | 4 ``` This works great, but I need the `linkable_type` to say 'Page' instead of '\Angel\Core\Page', and to be resolved from the IoC's 'Page' instead of being hard-coded to a particular namespaced class. What I've Tried: According to this question, it should be as easy as defining a `$morphClass` property to the linkable() classes, like so: ``` // \Angel\Core\Page protected $morphClass = 'Page'; ``` But when I apply this, and change the `menus_items` table to look like this: ``` linkable_type | linkable_id ---------------|-------------- Page | 11 Page | 4 ``` ...I simply get a `Class 'Page' not found.` error whenever linkable() is called on MenuItem. This is the exact line in Eloquent that throws the error. So, I dug into Eloquent and thought I might be able to get away with something like this: ``` // \Angel\Core\MenuItem public function linkable() { return $this->morphTo(null, App::make($this->linkable_type)); } ``` ...this feels so close, but alas: Eloquent calls linkable() before it's filled the rest of the MenuItem's attributes / columns, so $this->linkable_type is null and therefore will not resolve anything from the IoC. Thank you so much in advance for any guidance you might have!

Original source

Related problems