Laravel:返回多态关系的命名空间所有者

Laravel: Returning the namespaced owner of a polymorphic relation

我可以找到很多关于此的讨论,但没有明确的解决方案。这里有两个链接,虽然我会在这里涵盖我自己的问题中的所有内容。

Github Issues

Laravel.io discussion

简单说明

这是对已经熟悉 Laravel 的多态关系的任何人的问题的简单解释。

当使用 $morphClass 时,$morphClass 的内容作为变形 "type" 保存在数据库中,在尝试查找多态关系的所有者时用于 class 名称。这会导致错误,因为 $morphClass 的全部意义在于它不是 class.

的完全命名空间名称

如何定义多态关系应使用的class名称?

更详细的说明

这是一个更详细的解释,通过示例准确地解释了我正在尝试做的事情以及我为什么要这样做。

在 Laravel 中使用多态关系时,数据库中保存为 "morph_type" 类型的任何内容都假定为 class.

所以在这个例子中:

class Photo extends Eloquent {

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

}

class Staff extends Eloquent {

    public function photos()
    {
        return $this->morphOne('Photo', 'imageable');
    }

}

class Order extends Eloquent {

    public function photos()
    {
        return $this->morphOne('Photo', 'imageable');
    }

}

数据库看起来像这样:

staff

 - id - integer
 - name - string

orders

 - id - integer
 - price - integer

photos

 - id - integer
 - path - string
 - imageable_id - integer
 - imageable_type - string

现在第一行的照片可能是这样的:

id,path,imageable_id,imageable_type

1,image.png,1,Staff

现在我既可以从员工模型访问照片,也可以从照片模型访问员工。

//Find a staff member and dump their photos
$staff = Staff::find(1);

var_dump($staff->photos);

//Find a photo and dump the related staff member
$photo = Photo::find(1);

var_dump($photo->imageable);

到目前为止一切顺利。但是,当我为它们命名时,我 运行 遇到了问题。

namespace App/Store;
class Order {}

namespace App/Users;
class Staff {}

namespace App/Photos;
class Photo {}

现在我的数据库中保存的是:

id,path,imageable_id,imageable_type

1,image.png,1,App/Users/Staff

但我不想这样。像这样将完整的命名空间 class 名称保存在数据库中是个糟糕的主意!

幸运的是 Laravel 有一个设置 $morphClass 变量的选项。像这样:

class Staff extends Eloquent {

    protected $morphClass = 'staff';

    public function photos()
    {
        return $this->morphOne('Photo', 'imageable');
    }

}

现在我数据库中的行看起来像这样,太棒了!

id,path,imageable_id,imageable_type

1,image.png,1,staff

获取工作人员的照片绝对没问题。

//Find a staff member and dump their photos
$staff = Staff::find(1);

//This works!
var_dump($staff->photos);

然而,查找照片所有者的多态魔术不起作用:

//Find a photo and dump the related staff member
$photo = Photo::find(1);

//This doesn't work!
var_dump($photo->imageable);

//Error: Class 'staff' not found

大概必须有一种方法来通知多态关系在使用 $morphClass 时使用什么 classname 但我在文档、源代码或通过 Google.

有什么帮助吗?

有 2 种简单的方法 - 一种在下面,另一种在 @lukasgeiter 的回答中由 Taylor Otwell 提出,我绝对建议也检查一下:

// app/config/app.php or anywhere you like
'aliases' => [
    ...
    'MorphOrder' => 'Some\Namespace\Order',
    'MorphStaff' => 'Maybe\Another\Namespace\Staff',
    ...
]

// Staff model
protected $morphClass = 'MorphStaff';

// Order model
protected $morphClass = 'MorphOrder';

完成:

$photo = Photo::find(5);
$photo->imageable_type; // MorphOrder
$photo->imageable; // Some\Namespace\Order

$anotherPhoto = Photo::find(10);
$anotherPhoto->imageable_type; // MorphStaff
$anotherPhoto->imageable; // Maybe\Another\Namespace\Staff

我不会使用真实的 class 名称(OrderStaff)以避免可能的重复。某些东西被称为 MorphXxxx 的可能性很小,所以它非常安全。

比存储命名空间更好 (我不介意数据库中的外观,但是如果您更改某些内容会很不方便 - 说而不是 App\Models\User 使用 Cartalyst\Sentinel\User 等)因为 你所需要的只是通过别名配置交换实现

但是也有缺点 - 您不会知道模型是什么,只需检查数据库 - 以防它对您很重要。

让laravel把它放入db-命名空间和所有。如果除了使数据库更漂亮之外还需要短类名,请为类似的东西定义一个访问器:

<?php namespace App\Users;

class Staff extends Eloquent {

    // you may or may not want this attribute added to all model instances
    // protected $appends = ['morph_object'];

    public function photos()
    {
        return $this->morphOne('App\Photos\Photo', 'imageable');
    }

    public function getMorphObjectAttribute()
    {
        return (new \ReflectionClass($this))->getShortName();
    }

}

我总是在这种情况下回过头来的原因是 Laravel 经过了很好的测试,并且在大多数情况下都按预期工作。如果不需要,为什么要与框架作斗争——特别是如果你只是对数据库中的命名空间感到恼火。我同意这样做不是一个好主意,但我也觉得我可以花更多的时间来克服它并处理域代码。

我喜欢@JarekTkaczyks 解决方案,我建议您使用那个解决方案。但是,为了完整起见,Taylor 在 github

上简要提到了另一种方式

您可以为 imageable_type 属性添加属性访问器,然后使用“classmap”数组查找正确的 class.

class Photo extends Eloquent {

    protected $types = [
        'order' => 'App\Store\Order',
        'staff' => 'App\Users\Staff'
    ];

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

    public function getImageableTypeAttribute($type) {
        // transform to lower case
        $type = strtolower($type);

        // to make sure this returns value from the array
        return array_get($this->types, $type, $type);
        // for Laravel5.7 or later
        return \Arr::get($this->types, $type, $type);

        // which is always safe, because new 'class'
        // will work just the same as new 'Class'
    }

}

请注意您仍然需要 morphClass 属性来从关系的另一端进行查询。

为了预先加载多态关系,例如 Photo::with('imageable')->get(); 如果类型为空,则有必要 return null。

class Photo extends Eloquent {

    protected $types = [
        'order' => 'App\Store\Order',
        'staff' => 'App\Users\Staff'
    ];

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

    public function getImageableTypeAttribute($type) {
        // Illuminate/Database/Eloquent/Model::morphTo checks for null in order
        // to handle eager-loading relationships
        if(!$type) {
            return null;
        }

        // transform to lower case
        $type = strtolower($type);

        // to make sure this returns value from the array
        return array_get($this->types, $type, $type);

        // which is always safe, because new 'class'
        // will work just the same as new 'Class'
    }

}

使用 Laravel 5.2(或更新版本)时,您可以使用新功能 morphMap 来解决此问题。只需将此添加到 app/Providers/AppServiceProvider:

中的 boot 函数
Relation::morphMap([
    'post' => \App\Models\Post::class,
    'video' => \App\Models\Video::class,
]);

更多相关信息:https://nicolaswidart.com/blog/laravel-52-morph-map

这是您可以从 Eloquent 模型获得变形 class 名称(别名)的方法:

(new Post())->getMorphClass()

一个简单的解决方案是将数据库中 imageable_type 列中的值别名为完整的 class 命名空间,如:

// app/config/app.php 
'aliases' => [
...
'Order' => 'Some\Namespace\Order',
'Staff' => 'Maybe\Another\Namespace\Staff',
...
]