Php 从雄辩的模型中获取所有关系

Php 从雄辩的模型中获取所有关系,php,laravel-4,eloquent,Php,Laravel 4,Eloquent,拥有一个雄辩的模型,是否有可能在运行时获得它的所有关系及其类型 我试着看了一下,但是我找不到任何对这个场景有用的东西 例如,如果我们有经典的Post模型,有没有一种方法可以提取这样的关系 - belongsTo: User - belongsToMany: Tag 要实现这一点,您需要知道模型中方法的名称——它们可能会有很大的不同;) 想法: 如果在方法中有一个模式,比如relUser/relTag,那么可以将它们过滤掉 或者循环所有公共方法,查看是否弹出关系对象(坏主意) 您可以定义一个受

拥有一个雄辩的模型,是否有可能在运行时获得它的所有关系及其类型

我试着看了一下,但是我找不到任何对这个场景有用的东西

例如,如果我们有经典的
Post
模型,有没有一种方法可以提取这样的关系

- belongsTo: User
- belongsToMany: Tag

要实现这一点,您需要知道模型中方法的名称——它们可能会有很大的不同;)

想法:

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

  • 或者循环所有公共方法,查看是否弹出
    关系
    对象(坏主意)

  • 您可以定义一个
    受保护的$relationMethods
    (注意:Laravel已经使用了
    $relations
    )来保存一个带有方法的数组

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

[编辑:评论后]


如果型号配备了受保护的
$with=阵列(…)
然后,在加载记录后,您可以使用
$Model->getRelations()
查看加载的关系。当没有加载记录时,这是不可能的,因为还没有触及关系

getRelations()
位于
/vendor/laravel/framework/src/illighte/Database/Eloquent/Model.php


但目前它没有出现在laravel.com/api上的api中-这是因为我们得到了像Rob所说的更新版本。循环遍历每个公共方法并检查是否返回了关系是个坏主意

Barryvdh在他非常受欢迎的Laravel ide助手中使用了一种基于正则表达式的方法:

在调用getPropertiesFromMethods后,只需过滤接收到的属性,如下所示(未测试的示例):

有没有一种更干净的方法可以在不接触模型的情况下实现这一点


我认为我们必须等待PHP7(返回类型反射)或Taylor提供的新反射服务,我最近一直在做同样的事情,我认为没有反射是无法有效完成的。但这是一个资源密集型的问题,所以我应用了一些缓存。需要进行的一项检查是验证返回类型和预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;
}

我对我的项目也有同样的需求。我的解决方案是使用
get\u 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"
  ]
]
我需要它复制一个模型项目,包括关系

composer require adideas/laravel-get-relationship-eloquent-model

拉威尔得到了所有有口才的模特


要做到这一点,您不需要知道模型中方法的名称。有了一个或多个有说服力的模型,多亏了这个包,您可以在运行时获得它的所有关系及其类型

这个问题背后的要点是为其他需要了解关系的开发人员制作一个包。我不想强迫他们修改自己的类来适应这个包。如果没有其他选项,我将使用类似于您所指出的内部
$relations
然后,在加载记录后,您可以使用
$Model->getRelations()
查看加载的关系。当没有加载记录时,这是不可能的,因为还没有触及关系。哪里有
getRelations()
文档?我只能看到
getRelation($relation)
,但您必须传递关系名称。它位于Model.php(/vendor/laravel/framework/src/illighte/Database/elofunt/Model.php)中-但奇怪的是,它没有显示在api中-也许我有一个更新的版本。你是对的,我也有。如果你把它添加到答案中,我可以将它标记为有效。欢迎使用堆栈溢出!请小心链接到您自己的库,您不希望被视为一个用户。你应该在回答中说明你是该图书馆的附属机构。
composer require adideas/laravel-get-relationship-eloquent-model