如何用ReflectionClass获取模型关联且不执行指定方法?
我编写了一个静态方法definedRelations,仅用于获取模型的关联关系:
public static function definedRelations(): array { $model = new static; $relationships = []; $class = new \ReflectionClass($model); foreach($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { if ($method->class != get_class($model) || !empty($method->getParameters()) || $method->getName() == __FUNCTION__) { continue; } try { $return = $method->invoke($model); if ($return instanceof Relation) { $relationships[$method->getName()] = [ 'name' => $method->getName(), 'title' => ucwords(implode(' ', preg_split('~^[^A-Z]+\K|[A-Z][^A-Z]+\K~', $method->name, 0, PREG_SPLIT_NO_EMPTY))), 'type' => (new \ReflectionClass($return))->getShortName(), 'model' => (new \ReflectionClass($return->getRelated()))->getName() ]; } } catch(\Exception $ex) {} } return $relationships; }
问题在于调用ReflectionClass遍历并invoke方法时,会执行模型中的非关联方法(比如下面的recalculateInvoicing),产生不必要的数据库操作:
public function recalculateInvoicing(){ $invoice = $this->invoices()->firstOrNew(['charge_type_id' => 13]); $invoice->amount = $this->citations->where('is_active', 1)->sum('amount'); $this->invoices()->save($invoice); return $this; }
给非关联方法添加参数过滤
你的代码已经跳过带有参数的方法,只需给不需要执行的方法(如recalculateInvoicing)添加一个可选参数,就能被现有逻辑自动过滤:public function recalculateInvoicing($dummy = null){ // 原逻辑保持不变 }优点:改动最小,无需修改
definedRelations核心逻辑。用自定义注解标记关联方法
通过注解明确区分关联方法和其他公共方法,只处理带注解的方法:- 定义注解类:
#[Attribute(Attribute::TARGET_METHOD)] class RelationMethod {} - 给所有关联方法添加注解:
#[RelationMethod] public function invoices() { return $this->hasMany(Invoice::class); } - 修改
definedRelations中的判断逻辑,增加注解检查:// 在遍历方法时添加 $attributes = $method->getAttributes(RelationMethod::class); if (empty($attributes)) { continue; }
优点:逻辑清晰,从根源避免误执行非关联方法,扩展性强。
- 定义注解类:
利用返回类型特征过滤
Laravel关联方法的返回值都是Illuminate\Database\Eloquent\Relations\Relation的子类,可先通过反射检查方法的返回类型声明,再决定是否执行:// 替换原有try-catch块前的逻辑 $returnType = $method->getReturnType(); if (!$returnType || !is_subclass_of($returnType->getName(), \Illuminate\Database\Eloquent\Relations\Relation::class)) { continue; } // 后续再执行invoke并验证返回实例 try { $return = $method->invoke($model); if ($return instanceof Relation) { // 原有的记录逻辑 } } catch(\Exception $ex) {}优点:大幅减少不必要的方法执行,只针对可能是关联方法的进行调用。
手动维护关联方法列表
在模型中定义静态属性存储关联方法名,definedRelations直接遍历该列表,而非反射所有公共方法:// 模型类中添加静态属性 protected static $relationMethods = ['invoices', 'citations']; // 修改definedRelations方法 public static function definedRelations(): array { $model = new static; $relationships = []; $relationMethods = static::$relationMethods ?? []; foreach($relationMethods as $methodName) { if (!method_exists($model, $methodName)) continue; try { $return = $model->$methodName(); if ($return instanceof Relation) { // 原有的记录逻辑 } } catch(\Exception $ex) {} } return $relationships; }优点:完全避免误执行其他方法,逻辑最简单,但需要手动维护列表,适合关联方法变动少的场景。
通过命名规则过滤
如果你的关联方法严格遵循Laravel命名惯例(如复数对应hasMany,单数对应belongsTo),可以通过正则表达式过滤方法名:// 遍历方法时添加判断 if (!preg_match('/^[a-z]+(s)?$/', $method->getName())) { continue; }优点:无需修改其他方法,但依赖命名规则,灵活性差,容易出现漏判或误判。
内容的提问来源于stack exchange,提问作者Matthew

