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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27866020/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 10:39:54  来源:igfitidea点击:

Laravel: Returning the namespaced owner of a polymorphic relation

laravellaravel-4polymorphismeloquent

提问by John Mellor

I can find a number of discussions regarding this but no clear solution. Here are two links, although I will cover everything in my own question here.

我可以找到许多关于此的讨论,但没有明确的解决方案。这里有两个链接,尽管我将在这里介绍我自己的问题中的所有内容。

Github Issues

Github 问题

Laravel.io discussion

Laravel.io 讨论

Simple Explanation

简单说明

This is a simple explanation of my problem for anyone already familiar with Laravel's polymorphic relationships.

对于已经熟悉 Laravel 多态关系的人来说,这是对我的问题的简单解释。

When using $morphClass, the contents of $morphClass which is saved in the database as the morph "type" is used for the classname when trying to find the owner of a polymorphic relation. This results in an error since the whole point of $morphClass is that it is not a fully namespaced name of the class.

使用 $morphClass 时,在尝试查找多态关系的所有者时,将作为变形“类型”保存在数据库中的 $morphClass 的内容用于类名。这会导致错误,因为 $morphClass 的全部意义在于它不是类的完全命名空间名称。

How do you define the classname that the polymorphic relationship should use?

你如何定义多态关系应该使用的类名?

More Detailed Explanation

更详细的解释

This is a more detailed explanation explaining exactly what i'm trying to do and why i'm trying to do it with examples.

这是一个更详细的解释,准确地解释了我正在尝试做什么以及为什么我试图用例子来做。

When using Polymorphic relationships in Laravel whatever is saved as the "morph_type" type in the database is assumed to be the class.

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

So in this example:

所以在这个例子中:

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');
    }

}

The database would look like this:

数据库看起来像这样:

staff

 - id - integer
 - name - string

orders

 - id - integer
 - price - integer

photos

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

Now the first row of photos might look like this:

现在第一行照片可能如下所示:

id,path,imageable_id,imageable_type

1,image.png,1,Staff

id、路径、imageable_id、imageable_type

1,image.png,1,工作人员

Now I can either access the Photo from a Staff model or a Staff member from a Photo model.

现在,我可以从 Staff 模型访问 Photo,也可以从 Photo 模型访问 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);

So far so good. However when I namespace them I run into a problem.

到现在为止还挺好。但是,当我命名它们时,我遇到了问题。

namespace App/Store;
class Order {}

namespace App/Users;
class Staff {}

namespace App/Photos;
class Photo {}

Now what's saved in my database is this:

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

id,path,imageable_id,imageable_type

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

id、路径、imageable_id、imageable_type

1,image.png,1,App/用户/员工

But I don't want that. That's a terrible idea to have full namespaced class names saved in the database like that!

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

Fortunately Laravel has an option to set a $morphClass variable. Like so:

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

class Staff extends Eloquent {

    protected $morphClass = 'staff';

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

}

Now the row in my database will look like this, which is awesome!

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

id,path,imageable_id,imageable_type

1,image.png,1,staff

id、路径、imageable_id、imageable_type

1,image.png,1,工作人员

And getting the photos of a staff member works absolutely fine.

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

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

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

However the polymorphic magic of finding the owner of a photo doesn't work:

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

//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

Presumably there must be a way to inform the polymorphic relationship of what classname to use when using $morphClass but I cannot find any reference to how this should work in the docs, in the source code or via Google.

据推测,必须有一种方法来通知使用 $morphClass 时要使用的类名的多态关系,但我在文档、源代码或通过 Google 中找不到任何关于它应该如何工作的参考。

Any help?

有什么帮助吗?

回答by Jarek Tkaczyk

There are 2 easy ways - one below, other one in @lukasgeiter's answer as proposed by Taylor Otwell, which I definitely suggest checking as well:

有两种简单的方法 - 一种在下面,另一种在 @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';

done:

完成

$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

I wouldn't use real class names (Orderand Staff) to avoid possible duplicates. There's very little chance that something would be called MorphXxxxso it's pretty secure.

我不会使用真实的类名(OrderStaff)来避免可能的重复。调用某些东西的可能性很小,MorphXxxx因此非常安全。

This is better than storing namespaces(I don't mind the looks in the db, however it would be inconvenient in case you change something - say instead of App\Models\Useruse Cartalyst\Sentinel\Useretc) because all you need is to swap the implementationthrough aliases config.

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

However there is also downside - you won't know, what the model is, by just checking the db - in case it matters to you.

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

回答by dersvenhesse

When using Laravel 5.2 (or newer) you can use the new feature morphMapto address this issue. Just add this to the bootfunction in app/Providers/AppServiceProvider:

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

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

More about that: https://nicolaswidart.com/blog/laravel-52-morph-map

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

回答by lukasgeiter

I like @JarekTkaczyks solution and I would suggest you use that one. But, for the sake of completeness, there's is another way Taylor briefly mentions on github

我喜欢@JarekTkaczyks 解决方案,我建议你使用那个。但是,为了完整起见,Taylor 在github 上还简要提到了另一种方式

You can add a attribute accessor for the imageable_typeattribute and then use a "classmap" array to look up the right class.

您可以为该属性添加一个属性访问器imageable_type,然后使用“classmap”数组来查找正确的类。

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);

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

}

Notethat you still will need the morphClassattribute for querying from the other side of the relation though.

请注意,您仍然需要morphClass从关系的另一侧查询的属性。

回答by Abba Bryant

Let laravel put it into the db - namespace and all. If you need the short classname for something besides making your database prettier then define an accessor for something like :

让 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();
    }

}

The reasoning I always come back to in scenarios like this is that Laravel is pretty well tested, and works as expected for the most part. Why fight the framework if you don't have to - particularly if you are simply annoyed by the namespace being in the db. I agree it isn't a great idea to do so, but I also feel that I can spend my time more usefully getting over it and working on domain code.

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

回答by DTownsend

In order for eager loading of polymorphic relations eg Photo::with('imageable')->get();it's necessary to return null if type is empty.

为了预先加载多态关系,例如Photo::with('imageable')->get();,如果类型为空,则必须返回 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'
    }

}

回答by Daniel Petrovaliev

This is the way you can get morph class name(alias) from Eloquent model:

这是您可以从 Eloquent 模型中获取 morph 类名(别名)的方式:

(new Post())->getMorphClass()