自定义表单验证类的 $fail 该怎么响应

1. 运行环境

1). 当前使用的 Laravel 版本?

laravel 10.11

2. 问题描述?

自定义了一个验证类,用于验证选择的商品SKU能不能下单

namespace App\Rules;

use App\Models\Product\Product;
use App\Models\Product\Sku\ProductAttrItem;
use App\Services\Common\HashIdService;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class ProductSkuEnable implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $sku = ProductAttrItem::query()
            ->whereHashId($value)
            ->where('product_id', app(HashIdService::class)->decode(request()->get('product_id')))
            ->first();

        if (is_null($sku)) {
            $fail('产品属性已失效,请刷新页面重试');
        }

        if ($sku->stock < request()->get('quantity')) {
            $fail('库存不足');
        }
    }
}

测试发现,当 $sku 是 null 的时候,仍然会判断第二个 if,接着报错 Attempt to read property \"stock\" on null

如果是把这段代码写在 Request 类中闭包验证,可以通过 return $fail(...) 解决这个问题,但是在 Rule 类中 validate 方法是抽象类 ValidationRule 的接口,并且是被定义得空返回值,不能加 return

所以,是我这个验证类写的有问题吗

:computer: & :coffee:
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
最佳答案

你可以在$fail('产品属性已失效,请刷新页面重试');下一行加return;

1年前 评论
W-W (楼主) 1年前
讨论数量: 4

你可以在$fail('产品属性已失效,请刷新页面重试');下一行加return;

1年前 评论
W-W (楼主) 1年前

你可以elseif解决你现在这个问题 我刚刚大概看了一下这个源码部分 那个闭包就是处理了那个自定义验证的消息 添加到Validator的messages里面

protected function prepareRule($rule, $attribute)
 {
       ...

       //这里会把这个对象包装成InvokableValidationRule对象
        if (... || $rule instanceof ValidationRule) {
            $rule = InvokableValidationRule::make($rule);
        }

        ...

        return (string) $rule;
 }

public static function make($invokable)
{
        ...
        return new InvokableValidationRule($invokable);
}

进行验证的时候

protected function validateAttribute($attribute, $rule)
    {
        $this->currentRule = $rule;

        [$rule, $parameters] = ValidationRuleParser::parse($rule);

        ....

        if ($rule instanceof RuleContract) {
            return $validatable
                     //会调用自定义验证规则的方法
                    ? $this->validateUsingCustomRule($attribute, $value, $rule)
                    : null;
        }

     ...
}

protected function validateUsingCustomRule($attribute, $value, $rule)
    {
       ...
        //这里就是去验证
        if (! $rule->passes($attribute, $value)) {
            $ruleClass = $rule instanceof InvokableValidationRule ?
                get_class($rule->invokable()) :
                get_class($rule);

            $this->failedRules[$attribute][$ruleClass] = [];
            //会拿到自定义验证消息
            $messages = $this->getFromLocalArray($attribute, $ruleClass) ?? $rule->message();

            $messages = $messages ? (array) $messages : [$ruleClass];

            foreach ($messages as $key => $message) {
                $key = is_string($key) ? $key : $attribute;
               //添加到 Validator类的$messages属性里面  这样在异常捕获的时候可以拿到这个消息
                $this->messages->add($key, $this->makeReplacements(
                    $message, $key, $ruleClass, []
                ));
            }
        }
    }

看一下是怎么验证的 会调用Illuminate\Validation\InvokableValidationRule类的passes方法

public function passes($attribute, $value)
    {
        $this->failed = false;

        ...

        $method = $this->invokable instanceof ValidationRule
                        ? 'validate'
                        : '__invoke';
        //这里就是调用自定义验证规则类的validate方法
        $this->invokable->{$method}($attribute, $value, function ($attribute, $message = null) {
            $this->failed = true;
            //这里就是去翻译消息 添加到当前类的$messages属性上面
            return $this->pendingPotentiallyTranslatedString($attribute, $message);
        });

        return ! $this->failed;
    }

protected function pendingPotentiallyTranslatedString($attribute, $message)
    {
        $destructor = $message === null
            ? fn ($message) => $this->messages[] = $message
            : fn ($message) => $this->messages[$attribute] = $message;

        return new class($message ?? $attribute, $this->validator->getTranslator(), $destructor) extends PotentiallyTranslatedString
        {
            /**
             * The callback to call when the object destructs.
             *
             * @var \Closure
             */
            protected $destructor;

            /**
             * Create a new pending potentially translated string.
             *
             * @param  string  $message
             * @param  \Illuminate\Contracts\Translation\Translator  $translator
             * @param  \Closure  $destructor
             */
            public function __construct($message, $translator, $destructor)
            {
                parent::__construct($message, $translator);

                $this->destructor = $destructor;
            }

            /**
             * Handle the object's destruction.
             *
             * @return void
             */
            public function __destruct()
            {
                //这个对象在销毁的时候会去翻译消息中的变量
                ($this->destructor)($this->toString());
            }
        };
1年前 评论
W-W (楼主) 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!