【问题标题】:Load all relationships for a model加载模型的所有关系
【发布时间】:2015-02-28 19:09:16
【问题描述】:

通常为了急切地加载关系,我会这样做:

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

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

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

【问题讨论】:

  • 我认为 Laravel 会加载某个模型的所有关系,前提是它们是通过 has 方法定义的。
  • 体验一下。我想更新缓存的公司关系。这样就解决了。创建了更新特定关系的操作。每次我创建/更新关系时,我都会更新缓存的公司。

标签: php laravel


【解决方案1】:

composer 需要 adideas/laravel-get-relationship-eloquent-model

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

Laravel 获取所有 eloquent 模型的关系!

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

【讨论】:

    【解决方案2】:

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

    $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 的 Model 类有一个 load 和一个 loadMissing 方法用于预加载。

    api reference.

    【讨论】:

      【解决方案3】:

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

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

      您可以做的更简单的事情是定义一个添加所有关系的函数。 为此,您可以使用eloquents query scopes(感谢 Jarek Tkaczyk 在 cmets 中提到它)。

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

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

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

      获得支持关系的替代方法

      虽然 Laravel 不支持开箱即用的类似功能,但您始终可以自己添加。

      注释

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

      为每个关系添加注释:

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

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

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

      你当然也可以override with like lukasgeiter mentioned in his answer。这似乎比使用withAll 更干净。但是,如果您使用注释或配置数组,则见仁见智。

      【讨论】:

      • 如果方法不是静态的,我们不能像 Model::withAll() 那样使用
      • @DmytroGaliievskyi 我通常将模型注入到我的控制器中,因此我从不静态地使用它们,但我已解决此问题,感谢您的提示。
      • 不是这样,请改用scope,因为它不允许您这样做:Model::where(..)->...->withAll()->get()。但是 + 用于注释。
      • @JarekTkaczyk 感谢您注意到这一点,不知道范围,实际上它们很酷。更新了我的答案,当然也提到了你。
      • 晚会有点晚了,但我想你忘了在范围方法中返回$query。在documentation 中有这样的记录
      【解决方案4】:

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

      我所做的是首先创建一个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。

      【讨论】:

      • 嗯,它返回一个空数组[]
      • 而不是 in_array((string)$method->getReturnType(), self::$relationClasses) 我使用 is_a($classString, Relation::class,true) 因为所有的关系类都扩展了 Realtion::class 这工作
      【解决方案5】:

      我不会使用建议的 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

      【讨论】:

        【解决方案6】:

        您不能为特定模型动态加载关系。您需要告诉模型要支持哪些关系。

        【讨论】:

          【解决方案7】:

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

          基础模型

          我有点感觉您想在多个模型中执行此操作,因此如果您还没有创建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();
          

          【讨论】:

          • 是的,你说得对,我确实想在多个模型上使用它。在阅读您的答案并让一切正常工作之前,我使用了$relationships 而不是$relations。我正在研究 Model 类文档,但我仍然对 $relations 应该用于什么感到困惑......
          • $relationships 也可以,没错。 Laravel 使用 $relations 来存储加载后的关系。
          • 注意它不会影响Eloquent\Builder 上的with 方法,所以你不能这样做:Model::where(..)->with('*')->get()
          • 谁反对这个?在我看来,“not perfect”不等于“not有用”或“wrong”,因此远不能提供拒绝投票的理由...(这甚至不是我的答案)
          • @MarcelGwerder 我不知道也不能真正理解它。不过,您的答案看起来更好(使用您之后添加的内容)+1
          【解决方案8】:

          您可以在模型中创建方法

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

          并致电Model::withAllRelations()

          或者

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

          【讨论】:

            猜你喜欢
            • 2021-07-19
            • 2017-07-26
            • 2018-02-01
            • 1970-01-01
            • 2023-03-18
            • 1970-01-01
            • 2018-05-26
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多