![](/img/trans.png)
[英]Get eloquent model and all its relationships with one query in Laravel
[英]Get all relationships from Eloquent model
拥有一个 Eloquent model,是否可以在运行时获取其所有关系及其类型?
我试过看一下ReflectionClass
,但我找不到任何对这种情况有用的东西。
例如,如果我们有经典的Post
model,有没有办法像这样提取关系?
- belongsTo: User
- belongsToMany: Tag
为了实现这一点,您需要知道模型中方法的名称 - 它们可以有很大的不同 ;)
想法:
如果你在方法中有一个模式,比如 relUser / relTag,你可以过滤掉它们
或者循环遍历所有公共方法,看看是否弹出一个Relation
对象(坏主意)
你可以定义一个protected $relationMethods
(注意:Laravel 已经使用了$relations
)它保存了一个带有方法的数组。
调用 Post->User() 后,您将收到一个BelongsTo
或Relation
系列中的其他对象之一,因此您可以列出关系类型。
[编辑:评论后]
如果模型配备了受保护的$with = array(...);
然后您可以在加载记录后使用$Model->getRelations()
查看加载的关系。 当没有加载记录时这是不可能的,因为尚未触及关系。
getRelations()
在/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
但目前它没有出现在 laravel.com/api 的 api 中 - 这是因为我们有更新的版本
就像罗布所说的那样。 循环遍历每个公共方法并检查是否返回关系是一个坏主意。
Barryvdh 在他非常受欢迎的 Laravel-ide-helper 中使用了基于正则表达式的方法: 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 的新反射服务 ^^
我最近一直在做同样的事情,我认为没有 Reflection 就无法有效地完成它。 但这有点资源密集型,所以我应用了一些缓存。 需要进行的一项检查是验证返回类型,在 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_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
https://packagist.org/packages/adideas/laravel-get-relationship-eloquent-model
Laravel 获得所有雄辩模型的关系!
您无需知道模型中方法的名称即可执行此操作。 拥有一个或多个 Eloquent 模型,多亏了这个包,你可以在运行时获得它的所有关系和它们的类型
我知道它有点晚了,但我已经多次访问这个问题,所以想分享我的观察结果,以帮助那些将来访问这个问题的人。
这是我用来从 eloquent model class 中提取关系的方法。
/**
*
* Returns all the relationship methods defined
* in the provided model class with related
* model class and relation function name
*
* @param string $modelClass exampe: App\Models\Post
* @return array $relattions array containing information about relationships
*/
protected function getModelRelationshipMethods(string $modelClass)
{
//can define this at class level
$relationshipMethods = [
'hasMany',
'hasOne',
'belongsTo',
'belongsToMany',
];
$reflector = new ReflectionClass($modelClass);
$path = $reflector->getFileName();
//lines of the file
$lines = file($path);
$methods = $reflector->getMethods();
$relations = [];
foreach ($methods as $method) {
//if its a concrete class method
if ($method->class == $modelClass) {
$start = $method->getStartLine();
$end = $method->getEndLine();
//loop through lines of the method
for($i = $start-1; $i<=$end-1; $i++) {
// look for text between -> and ( assuming that its on one line
preg_match('~\->(.*?)\(~', $lines[$i], $matches);
// if there is a match
if (count($matches)) {
//loop to check if the found text is in relationshipMethods list
foreach ($matches as $match) {
// if so add it to the output array
if (in_array($match, $relationshipMethods)) {
$relations[] = [
//function name of the relation definition
'method_name' => $method->name,
//type of relation
'relation' => $match,
//related Class name
'related' => (preg_match('/'.$match.'\((.*?),/', $lines[$i], $related) == 1) ? $related[1] : null,
];
}
}
}
}
}
}
return $relations;
}
如果您 dd() 或 dump() 为 App/Post model 返回的 $relations,则 output 将是这样的
^ array:3 [
0 => array:3 [
"method_name" => "user"
"relation" => "belongsTo"
"related" => "User::class"
]
1 => array:3 [
"method_name" => "tag"
"relation" => "belongsToMany"
"related" => "Tag::class"
]
2 => array:3 [
"method_name" => "comments"
"relation" => "hasMany"
"related" => "Comment::class"
]
]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.