Laravel:返回多态关系的命名空间所有者
Laravel: Returning the namespaced owner of a polymorphic relation
我可以找到很多关于此的讨论,但没有明确的解决方案。这里有两个链接,虽然我会在这里涵盖我自己的问题中的所有内容。
简单说明
这是对已经熟悉 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 名称(Order
和 Staff
)以避免可能的重复。某些东西被称为 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,
]);
这是您可以从 Eloquent 模型获得变形 class 名称(别名)的方法:
(new Post())->getMorphClass()
一个简单的解决方案是将数据库中 imageable_type 列中的值别名为完整的 class 命名空间,如:
// app/config/app.php
'aliases' => [
...
'Order' => 'Some\Namespace\Order',
'Staff' => 'Maybe\Another\Namespace\Staff',
...
]
我可以找到很多关于此的讨论,但没有明确的解决方案。这里有两个链接,虽然我会在这里涵盖我自己的问题中的所有内容。
简单说明
这是对已经熟悉 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 名称(Order
和 Staff
)以避免可能的重复。某些东西被称为 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,
]);
这是您可以从 Eloquent 模型获得变形 class 名称(别名)的方法:
(new Post())->getMorphClass()
一个简单的解决方案是将数据库中 imageable_type 列中的值别名为完整的 class 命名空间,如:
// app/config/app.php
'aliases' => [
...
'Order' => 'Some\Namespace\Order',
'Staff' => 'Maybe\Another\Namespace\Staff',
...
]