【问题标题】:Get all relationships from Eloquent model从 Eloquent 模型中获取所有关系
【发布时间】:2013-12-02 18:02:11
【问题描述】:

拥有一个 Eloquent 模型,是否可以在运行时获取其所有关系及其类型?

我尝试查看ReflectionClass,但找不到任何对这种情况有用的东西。

例如,如果我们有经典的Post 模型,有没有办法提取这样的关系?

- belongsTo: User
- belongsToMany: Tag

【问题讨论】:

    标签: php laravel-4 eloquent


    【解决方案1】:

    要做到这一点,您将知道模型中方法的名称 - 它们可能会有很大差异;)

    想法:

    • 如果你在方法中有一个模式,比如 relUser / relTag,你可以过滤掉它们

    • 或者循环遍历所有公共方法,看看有没有Relation对象弹出(坏主意)

    • 你可以定义一个protected $relationMethods(注意:Laravel 已经使用$relations),它包含一个带有方法的数组。

    调用 Post->User() 后,您将收到 BelongsToRelation 系列中的其他对象之一,因此您可以列出关系类型。

    [编辑:在 cmets 之后]

    如果模型配备了受保护的$with = array(...);,那么您可以在加载记录后查看与$Model->getRelations() 的加载关系。当没有加载记录时这是不可能的,因为关系还没有被触及。

    getRelations()/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php

    但目前它没有显示在 laravel.com/api 的 api 中 - 这是因为我们获得了更新的版本

    【讨论】:

    • 这个问题的重点是为其他需要了解关系的开发人员制作一个包。我不想强迫他们修改他们的类只是为了适应包。如果没有其他选择,我将最终使用类似于内部$relations 的东西,正如你所指出的。
    • 如果模型配备了protected $with = array(...);,那么您可以在加载记录后查看与$Model->getRelations() 的加载关系。当没有加载记录时这是不可能的,因为关系还没有被触及。
    • getRelations() 记录在哪里?我只能看到getRelation($relation),但是你必须传递关系名称。
    • 它在 Model.php (/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php) - 但奇怪的是它没有出现在 api 中laravel.com/api - 也许我有一个更新的版本。
    • 你说得对,我也明白了。如果您将其添加到答案中,我可以将其标记为有效。
    【解决方案2】:

    就像 Rob 所说的那样。循环遍历每个公共方法并检查是否返回了关系是一个坏主意。

    Barryvdh 在他非常受欢迎的 Laravel-ide-helper 中使用了基于 Regex 的方法: https://github.com/barryvdh/laravel-ide-helper/blob/master/src/Console/ModelsCommand.php

    您只需像这样(未经测试的示例)过滤调用 getPropertiesFromMethods 后收到的属性:

    class classSniffer{
        private $properties = [];
    
        //...
    
        public function getPropertiesFromMethods($model){
            //the copied code from the class above (ModelsCommand@getPropertiesFromMethods)
        }
    
        public function getRelationsFrom($model){
            $this->getPropertiesFromMethods($model);
    
            $relations = [];
    
            foreach($this->properties as $name => $property){
                $type = $property;
    
                $isRelation = strstr($property[$type], 'Illuminate\Database\Eloquent\Relations');
                if($isRelation){
                    $relations[$name] = $property;
                }            
            }
    
            return $relations;
        }
    }
    

    有没有更简洁的方法可以在不接触模型的情况下做到这一点?

    我认为我们必须等待 PHP7(返回类型反射)或 Taylor 提供的新反射服务^^

    【讨论】:

      【解决方案3】:

      我最近一直在做同样的事情,我认为没有反射就无法有效地完成。但这有点占用资源,所以我应用了一些缓存。需要的一项检查是验证返回类型和 pre-php7,这只能通过实际执行每个方法来完成。因此,我还应用了一些逻辑,在运行该检查之前减少了可能的候选人数量。

      /**
       * Identify all relationships for a given model
       *
       * @param   object  $model  Model
       * @param   string  $heritage   A flag that indicates whether parent and/or child relationships should be included
       * @return  array
       */
      public function getAllRelations(\Illuminate\Database\Eloquent\Model $model = null, $heritage = 'all')
      {
          $model = $model ?: $this;
          $modelName = get_class($model);
          $types = ['children' => 'Has', 'parents' => 'Belongs', 'all' => ''];
          $heritage = in_array($heritage, array_keys($types)) ? $heritage : 'all';
          if (\Illuminate\Support\Facades\Cache::has($modelName."_{$heritage}_relations")) {
              return \Illuminate\Support\Facades\Cache::get($modelName."_{$heritage}_relations"); 
          }
      
          $reflectionClass = new \ReflectionClass($model);
          $traits = $reflectionClass->getTraits();    // Use this to omit trait methods
          $traitMethodNames = [];
          foreach ($traits as $name => $trait) {
              $traitMethods = $trait->getMethods();
              foreach ($traitMethods as $traitMethod) {
                  $traitMethodNames[] = $traitMethod->getName();
              }
          }
      
          // Checking the return value actually requires executing the method.  So use this to avoid infinite recursion.
          $currentMethod = collect(explode('::', __METHOD__))->last();
          $filter = $types[$heritage];
          $methods = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);  // The method must be public
          $methods = collect($methods)->filter(function ($method) use ($modelName, $traitMethodNames, $currentMethod) {
              $methodName = $method->getName();
              if (!in_array($methodName, $traitMethodNames)   //The method must not originate in a trait
                  && strpos($methodName, '__') !== 0  //It must not be a magic method
                  && $method->class === $modelName    //It must be in the self scope and not inherited
                  && !$method->isStatic() //It must be in the this scope and not static
                  && $methodName != $currentMethod    //It must not be an override of this one
              ) {
                  $parameters = (new \ReflectionMethod($modelName, $methodName))->getParameters();
                  return collect($parameters)->filter(function ($parameter) {
                      return !$parameter->isOptional();   // The method must have no required parameters
                  })->isEmpty();  // If required parameters exist, this will be false and omit this method
              }
              return false;
          })->mapWithKeys(function ($method) use ($model, $filter) {
              $methodName = $method->getName();
              $relation = $model->$methodName();  //Must return a Relation child. This is why we only want to do this once
              if (is_subclass_of($relation, \Illuminate\Database\Eloquent\Relations\Relation::class)) {
                  $type = (new \ReflectionClass($relation))->getShortName();  //If relation is of the desired heritage
                  if (!$filter || strpos($type, $filter) === 0) {
                      return [$methodName => get_class($relation->getRelated())]; // ['relationName'=>'relatedModelClass']
                  }
              }
              return false;   // Remove elements reflecting methods that do not have the desired return type
          })->toArray();
      
          \Illuminate\Support\Facades\Cache::forever($modelName."_{$heritage}_relations", $methods);
          return $methods;
      }
      

      【讨论】:

        【解决方案4】:

        我的项目也有同样的需求。我的解决方案是使用get_class 函数来检查关系类型。示例:

         $invoice = App\Models\Invoice::with('customer', 'products', 'invoiceProducts', 'invoiceProduct')->latest()->first();
        
            foreach ($invoice->getRelations() as $relation => $items) {
                $model = get_class($invoice->{$relation}());
                $type  = explode('\\', $model);
                $type  = $type[count($type) - 1];
        
                $relations[] = ['name' => $relation, 'type' => $type];
            }
            dd($relations);
        

        示例结果:

        array:4 [▼
          0 => array:2 [▼
            "name" => "customer"
            "type" => "BelongsTo"
          ]
          1 => array:2 [▼
            "name" => "products"
            "type" => "BelongsToMany"
          ]
          2 => array:2 [▼
            "name" => "invoiceProducts"
            "type" => "HasMany"
          ]
          3 => array:2 [▼
            "name" => "invoiceProduct"
            "type" => "HasOne"
          ]
        ]
        

        我需要它来复制包含关系的模型项

        【讨论】:

          【解决方案5】:
          composer require adideas/laravel-get-relationship-eloquent-model
          

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

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

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

          【讨论】:

          • 欢迎来到 Stack Overflow!请小心链接到您自己的库,您不想被视为spammer。您应该在回答中明确说明您隶属于该图书馆。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-03-23
          • 2017-01-12
          • 1970-01-01
          • 1970-01-01
          • 2014-03-04
          • 1970-01-01
          • 2017-12-19
          相关资源
          最近更新 更多