You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

编写PHPStan自定义规则限制Cart::save()仅从CartRepository调用

问题:PHPStan自定义规则误判非Cart类的save()调用

需求概述

禁止在代码库任意位置调用Cart::save()(即$cart->save()),仅允许从CartRepository类调用该方法;外部调用时抛出错误:"Calling Cart::save() method outside of CartRepository class is not allowed."

现有规则问题

当前规则会误判:在Cart类的updateVariant方法中调用$variant->save()(Variant类的save方法)时,也触发错误。原因是规则仅检查调用发起类为Cart,未验证被调用的对象是否是Cart实例,导致所有在Cart类内调用的save()方法都被误判。

现有规则代码

class CartSaveRule implements Rule
{
    public function getNodeType(): string
    {
        return MethodCall::class;
    }

    /**
     * @param MethodCall $node
     * @param Scope $scope
     * @return array
     */
    public function processNode(Node $node, Scope $scope): array
    {
        // Check if the method call is to the "save" method
        if ($node->name->toString() === 'save') {
            $className = $scope->getClassReflection()->getName();

            // Check if the method call is made from the Product class
            if ($className === 'Cart') {
                // Get the method call location
                $line = $node->getLine();
                $file = $scope->getFile();

                // Check if the method call is not made from inside the ProductRepository class
                if (!$this->isCalledFromProductRepository($scope)) {
                    return [
                        RuleErrorBuilder::message('Calling save() method of Cart class outside of CartRepository class is not allowed.')
                            ->line($line)
                            ->file($file)
                            ->build()
                    ];
                }
            }
        }

        return [];
    }


    private function isCalledFromRepository(Scope $scope): bool
    {
        // Check if the calling class is CartRepository or its subclass
        return  $scope->getClassReflection()->isSubclassOf('CartRepository');
    }
}

问题分析

  1. 对象类型判断缺失:仅检查调用发起类,未验证被调用对象是否为Cart实例,导致误判其他类的save()调用。
  2. 方法名笔误:isCalledFromProductRepository方法未定义,实际调用的是isCalledFromRepository,逻辑不一致。
  3. 逻辑颠倒:错误地将调用发起类为Cart作为触发条件,而非针对Cart实例的save()调用。

修正后的规则代码

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;

class CartSaveRule implements Rule
{
    private const ALLOWED_CLASS = 'CartRepository';
    private const TARGET_CLASS = 'Cart';
    private const ERROR_MESSAGE = 'Calling Cart::save() method outside of CartRepository class is not allowed.';

    public function getNodeType(): string
    {
        return MethodCall::class;
    }

    /**
     * @param MethodCall $node
     * @param Scope $scope
     * @return array<int, \PHPStan\Rules\RuleError>
     */
    public function processNode(Node $node, Scope $scope): array
    {
        // 1. 过滤非save方法的调用
        if ($node->name->toString() !== 'save') {
            return [];
        }

        // 2. 验证被调用对象是否为Cart类实例
        $calledType = $scope->getType($node->var);
        $calledClass = $calledType->getClassReflection();
        if ($calledClass === null || $calledClass->getName() !== self::TARGET_CLASS) {
            return [];
        }

        // 3. 检查调用上下文是否为允许的类
        $currentClass = $scope->getClassReflection();
        if ($currentClass === null) {
            // 全局代码中调用,直接报错
            return $this->createError($node, $scope);
        }

        if (!$currentClass->isSubclassOf(self::ALLOWED_CLASS) && $currentClass->getName() !== self::ALLOWED_CLASS) {
            // 非CartRepository及其子类中调用,报错
            return $this->createError($node, $scope);
        }

        return [];
    }

    /**
     * 生成错误信息
     */
    private function createError(MethodCall $node, Scope $scope): array
    {
        return [
            RuleErrorBuilder::message(self::ERROR_MESSAGE)
                ->line($node->getLine())
                ->file($scope->getFile())
                ->build()
        ];
    }
}

修正说明

  • 精准匹配对象类型:通过$scope->getType($node->var)获取被调用对象的类型,仅针对Cart实例的save()方法触发检查。
  • 完善上下文验证:检查调用所在类是否为CartRepository或其子类,同时处理全局代码中的调用场景。
  • 代码优化:用常量固定配置值,拆分错误生成逻辑,提升代码可读性和维护性。

内容的提问来源于stack exchange,提问作者Bogdan Dubyk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 01:55:23