加载模型的所有关系


Load all relationships for a model

通常为了急于加载关系,我会做这样的事情:

Model::with('foo', 'bar', 'baz')...

一个解决方案可能是设置$with = ['foo','bar','baz']但是,每当我调用时,这将始终加载这三个关系Model

是否可以做这样的事情:Model::with('*')

不,它

不是,至少没有一些额外的工作,因为你的模型在实际加载之前不知道它支持哪些关系。

我在我自己的一个Laravel软件包中遇到了这个问题。无法获得模型与 Laravel 的关系列表。不过,如果你看看它们是如何定义的,这是很明显的。返回Relation对象的简单函数。你甚至无法使用 php 的反射类获取函数的返回类型,因此无法区分关系函数和任何其他函数。

为了简化它,您可以做的是定义一个添加所有关系的函数。为此,您可以使用雄辩查询范围(感谢Jarek Tkaczyk在评论中提到它)。

public function scopeWithAll($query) 
{
    $query->with('foo', 'bar', 'baz');
}

使用作用域而不是静态函数不仅可以直接在模型上使用函数,还可以在以任何顺序链接查询生成器方法(如where)时使用函数:

Model::where('something', 'Lorem ipsum dolor')->withAll()->where('somethingelse', '>', 10)->get();

获得支持关系的替代方案

虽然Laravel不支持开箱即用的东西,但你可以自己添加它。

附注

我使用注释来确定函数是否是上面提到的包中的关系。注释不是 php 的正式组成部分,但很多人使用文档块来模拟它们。Laravel 5 也将在其路由定义中使用注释,因此我认为在这种情况下这不是不好的做法。优点是,您不需要维护受支持关系的单独列表。

为每个关系添加注释:

/**
 * @Relation
 */
public function foo() 
{
    return $this->belongsTo('Foo');
}

编写一个函数,用于解析模型中所有方法的文档块并返回名称。您可以在模型或父类中执行此操作:

public static function getSupportedRelations() 
{
    $relations = [];
    $reflextionClass = new ReflectionClass(get_called_class());
    foreach($reflextionClass->getMethods() as $method) 
    {
        $doc = $method->getDocComment();
        if($doc && strpos($doc, '@Relation') !== false) 
        {
            $relations[] = $method->getName();
        }
    }
    return $relations;
}

然后只需在withAll函数中使用它们:

public function scopeWithAll($query) 
{
    $query->with($this->getSupportedRelations());
}

有些喜欢 php 中的注释,有些不喜欢。我喜欢这个简单的用例。

支持的关系数组

您还可以维护所有受支持的关系的数组。但是,这需要您始终将其与可用关系同步,尤其是在涉及多个开发人员的情况下,并不是那么容易。

protected $supportedRelations = ['foo','bar', 'baz'];

然后只需在withAll函数中使用它们:

public function scopeWithAll($query) 
{
    return $query->with($this->supportedRelations);
}

当然,你也可以覆盖with,比如卢卡斯盖特在他的回答中提到的。这似乎比使用 withAll 更干净。但是,如果您使用注释或配置数组,则是一个意见问题。

如果不自己指定它们,就无法知道所有关系是什么。发布的其他答案如何很好,但我想补充一些东西。

基本型号

我有点感觉你想在多个模型中做到这一点,所以一开始我会创建一个BaseModel如果你还没有的话。

class BaseModel extends Eloquent {
    public $allRelations = array();
}

"配置"数组

与其将关系硬编码到方法中,我建议您使用成员变量。正如您在上面看到的,我已经添加了$allRelations.请注意,您不能$relations命名它,因为Laravel已经在内部使用它。

覆盖with()

既然你想要with(*)你也可以这样做。将此添加到BaseModel

public static function with($relations){
    $instance = new static;
    if($relations == '*'){
        $relations = $instance->allRelations;
    }
    else if(is_string($relations)){
        $relations = func_get_args();
    }
    return $instance->newQuery()->with($relations);
}

(顺便说一下,这个函数的某些部分来自原始的 Model 类)

用法

class MyModel extends BaseModel {
    public $allRelations = array('foo', 'bar');
}
MyModel::with('*')->get();

由于我遇到了类似的问题,并且找到了一个很好的解决方案,这里没有描述,也不需要填充一些自定义数组或其他任何东西,我将在将来发布它。

我所做的,是首先创建一个trait,称为RelationsManager

trait RelationsManager
{
    protected static $relationsList = [];
    protected static $relationsInitialized = false;
    protected static $relationClasses = [
        HasOne::class,
        HasMany::class,
        BelongsTo::class,
        BelongsToMany::class
    ];
    public static function getAllRelations($type = null) : array
    {
        if (!self::$relationsInitialized) {
            self::initAllRelations();
        }
        return $type ? (self::$relationsList[$type] ?? []) : self::$relationsList;
    }
    protected static function initAllRelations()
    {
        self::$relationsInitialized = true;
        $reflect = new ReflectionClass(static::class);
        foreach($reflect->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
            /** @var ReflectionMethod $method */
            if ($method->hasReturnType() && in_array((string)$method->getReturnType(), self::$relationClasses)) {
                self::$relationsList[(string)$method->getReturnType()][] = $method->getName();
            }
        }
    }
    public static function withAll() : Builder
    {
        $relations = array_flatten(static::getAllRelations());
        return $relations ? self::with($relations) : self::query();
    }
}

现在你可以将它与任何类一起使用,比如 -

class Project extends Model
{
    use RelationsManager;
    //... some relations
}

然后,当您需要从数据库中获取它们时:

$projects = Project::withAll()->get();

一些注释 - 我的示例关系类列表不包括形态关系,所以如果你也想得到它们 - 你需要将它们添加到$relationClasses变量中。此外,此解决方案仅适用于 PHP 7。

我不会使用建议static方法,因为...这是雄辩的;)只需利用它已经提供的功能 - 范围

当然,它不会为您做(主要问题),但这绝对是要走的路:

// SomeModel
public function scopeWithAll($query)
{
    $query->with([ ... all relations here ... ]); 
    // or store them in protected variable - whatever you prefer
    // the latter would be the way if you want to have the method
    // in your BaseModel. Then simply define it as [] there and use:
    // $query->with($this->allRelations); 
}

这样,您可以根据需要自由使用它:

// static-like
SomeModel::withAll()->get();
// dynamically on the eloquent Builder
SomeModel::query()->withAll()->get();
SomeModel::where('something', 'some value')->withAll()->get();

此外,事实上你可以让Eloquent为你做这件事,就像Doctrine一样 - 使用doctrine/annotations和DocBlocks。你可以做这样的事情:

// SomeModel
/**
 * @Eloquent'Relation
 */
public function someRelation()
{
  return $this->hasMany(..);
}
故事太长了,

无法将其包含在此处,因此请了解其工作原理:http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html

您可以尝试使用反射来检测特定于模型的方法,例如:

$base_methods = get_class_methods('Illuminate'Database'Eloquent'Model');
$model_methods = get_class_methods(get_class($entry));
$maybe_relations = array_diff($model_methods, $base_methods);
dd($maybe_relations);

然后尝试在控制良好的try/catch中加载每个。Laravel的模型类有一个负载和一个负载缺少用于预先加载的方法。

请参阅 api 参考。

您可以在模型中创建方法

public static function withAllRelations() {
   return static::with('foo', 'bar', 'baz');
}

并打电话给Model::withAllRelations()

$instance->withAllRelations()->first(); // or ->get()

不能对某个模型动态加载关系。 您需要告诉模型要支持哪些关系。

作曲家需要adideas/laravel-get-relations-eloquent-model

https://packagist.org/packages/adideas/laravel-get-relationship-eloquent-model

拉维尔得到关系所有雄辩的模特!

您无需知道模型中方法的名称即可执行此操作。拥有一个或多个 Eloquent 模型,借助此软件包,您可以在运行时获得其所有关系及其类型

最佳解决方案

首先创建一个特征,称为关系管理器:

<?php
namespace App'Traits;
use Illuminate'Database'Eloquent'Builder;
use Illuminate'Database'Eloquent'Relations'BelongsTo;
use Illuminate'Database'Eloquent'Relations'BelongsToMany;
use Illuminate'Database'Eloquent'Relations'HasMany;
use Illuminate'Database'Eloquent'Relations'HasManyThrough;
use Illuminate'Database'Eloquent'Relations'HasOne;
use Illuminate'Database'Eloquent'Relations'HasOneThrough;
use Illuminate'Database'Eloquent'Relations'MorphMany;
use Illuminate'Database'Eloquent'Relations'MorphOne;
use Illuminate'Database'Eloquent'Relations'MorphTo;
use Illuminate'Database'Eloquent'Relations'MorphToMany;
use ReflectionClass;
use ReflectionMethod;
trait RelationsManager
{
    protected static $relationsList = [];
    protected static $relationsInitialized = false;
    protected static $relationClasses = [
        HasOne::class,
        HasMany::class,
        BelongsTo::class,
        BelongsToMany::class,
        HasOneThrough::class,
        HasManyThrough::class,
        MorphTo::class,
        MorphOne::class,
        MorphMany::class,
        MorphToMany::class,
    ];
    public static function getAllRelations($type = null): array
    {
        if (!self::$relationsInitialized) {
            self::initAllRelations();
        }
        return $type ? (self::$relationsList[$type] ?? []) : self::$relationsList;
    }
    protected static function initAllRelations()
    {
        self::$relationsInitialized = true;
        $reflect = new ReflectionClass(static::class);
        foreach ($reflect->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
            /** @var ReflectionMethod $method */
            if ($method->hasReturnType() && in_array((string) $method->getReturnType(), self::$relationClasses)) {
                self::$relationsList[(string) $method->getReturnType()][] = $method->getName();
            }
        }
    }
    public static function withAll(): Builder
    {
        $relations = array_flatten(static::getAllRelations());
        return $relations ? self::with($relations) : self::query();
    }
}

现在你可以将它与任何类一起使用,比如 -

class Company extends Model
{
    use RelationsManager;
    //... some relations
}

然后,当您需要从数据库中获取它们时:

$companies = Company::withAll()->get();

此解决方案仅适用于 PHP 7 或更高版本。