没有不值得去解决的问题,也没有不值得去学习的技术!

Validating Field Uniqueness in Laravel 9: Passing Request Input to the Ignore Method

When a duplicate value for logistics_freight_no exists in the table, validation fails with the message: The freight number has already been taken

1. The request parameters are as follows, and the uniqueness of logistics_freight_no in the table must be ensured. Since batch overwrite writing is supported, the final implementation plan is to first delete all records where return_order_id is 28, and then rewrite them all.

JSON
{
    "return_order_id": 28,
    "data": [
        {
            "logistics_company": 65656,
            "logistics_freight_no": 1
        },
        {
            "logistics_company": 65656,
            "logistics_freight_no": 2
        }
    ]
}

2. The validation rules are implemented as follows

PHP
$validator = Validator::make(
	$params,
	[
		'return_order_id' => [
			'required',
			'numeric',
			'integer',
			Rule::exists('Modules\Order\Models\ReturnOrder', 'id')
		],
		'data' => [
			'required',
			'array'
		],
		'data.*.logistics_company' => 'max::255',
		'data.*.logistics_freight_no' => [
			'required',
			'distinct',
			'max::255',
			Rule::unique('Modules\Order\Models\ReturnOrderLogistic'),
		]
	],
	[
		'return_order_id.required' => '请选择退货订单ID',
		'return_order_id.numeric' => '退货订单ID必须是数字',
		'return_order_id.integer' => '退货订单ID必须是整数',
		'return_order_id.exists' => '退货订单ID不存在',
		'data.required' => '请填写退货订单物流信息',
		'data.array' => '退货订单物流信息必须是数组',
		'data.*.logistics_company.max' => '物流公司最大长度是255',
		'data.*.logistics_freight_no.required' => '请填写货运单号',
		'data.*.logistics_freight_no.distinct' => '货运单号不允许重复',
		'data.*.logistics_freight_no.max' => '货运单号最大长度是255',
		'data.*.logistics_freight_no.unique' => '货运单号已被占用'
	]
);

3. When a duplicate value for logistics_freight_no exists in the table, validation fails with the message: The freight number has already been taken. As shown in Figure 1

When a duplicate value for logistics_freight_no exists in the table, validation fails with the message: The freight number has already been taken
Figure 1

4. Reference: Forcing A Unique Rule To Ignore A Given ID. However, note: You should not pass any user-controlled request input to the ignore method. Instead, you should only pass a system-generated unique ID such as an auto-incrementing ID or UUID from an Eloquent model instance. Otherwise, your application will be vulnerable to SQL injection attacks. I had to pass the request input return_order_id to the ignore method, but I can validate in advance whether return_order_id is an auto-incrementing ID of an Eloquent model instance. To prevent SQL injection attacks, I decided to cast it to the int type

PHP
$validator = Validator::make(
	$params,
	[
		'return_order_id' => [
			'required',
			'numeric',
			'integer',
			Rule::exists('Modules\Order\Models\ReturnOrder', 'id')
		],
		'data' => [
			'required',
			'array'
		],
		'data.*.logistics_company' => 'max::255',
		'data.*.logistics_freight_no' => [
			// 如果退货订单ID不存在,则不验证,防止报错:Undefined array key "return_order_id"
			'exclude_without:return_order_id',
			'required',
			'distinct',
			'max::255',
			Rule::unique('Modules\Order\Models\ReturnOrderLogistic')->ignore((int)$params['return_order_id'], 'return_order_id')
		]
	],
	[
		'return_order_id.required' => '请选择退货订单ID',
		'return_order_id.numeric' => '退货订单ID必须是数字',
		'return_order_id.integer' => '退货订单ID必须是整数',
		'return_order_id.exists' => '退货订单ID不存在',
		'data.required' => '请填写退货订单物流信息',
		'data.array' => '退货订单物流信息必须是数组',
		'data.*.logistics_company.max' => '物流公司最大长度是255',
		'data.*.logistics_freight_no.required' => '请填写货运单号',
		'data.*.logistics_freight_no.distinct' => '货运单号不允许重复',
		'data.*.logistics_freight_no.max' => '货运单号最大长度是255',
		'data.*.logistics_freight_no.unique' => '货运单号已被占用'
	]
);

5. After the first request succeeds, the second request can still succeed even with a duplicate logistics_freight_no. The final executed SQL is as follows, which meets expectations

JSON
{
    "return_order_id": 28,
    "data": [
        {
            "logistics_company": 65656,
            "logistics_freight_no": 1
        },
        {
            "logistics_company": 65656,
            "logistics_freight_no": 2
        },
        {
            "logistics_company": 65656,
            "logistics_freight_no": 24
        }
    ]
}

SQL
select count(*) as aggregate from `return_orders` where `id` = 28
 
select
  count(*) as aggregate
from
  `return_order_logistics`
where
  `logistics_freight_no` = 1
  and `return_order_id` <> '28'
 
select
  count(*) as aggregate
from
  `return_order_logistics`
where
  `logistics_freight_no` = 2
  and `return_order_id` <> '28'
 
select
  count(*) as aggregate
from
  `return_order_logistics`
where
  `logistics_freight_no` = 24
  and `return_order_id` <> '28'
 
delete from `return_order_logistics` where `return_order_id` = 28
 
insert into
  `return_order_logistics` (
    `created_at_gmt`,
    `logistics_company`,
    `logistics_freight_no`,
    `return_order_id`,
    `updated_at_gmt`
  )
values
  (
    '2024-04-25 02:23:41',
    65656,
    1,
    28,
    '2024-04-25 02:23:41'
  ),
  (
    '2024-04-25 02:23:41',
    65656,
    2,
    28,
    '2024-04-25 02:23:41'
  ),
  (
    '2024-04-25 02:23:41',
    65656,
    24,
    28,
    '2024-04-25 02:23:41'
  )

6. Then, when return_order_id is not 28, validation fails with: The freight number has already been taken, which meets expectations. The executed SQL is as follows

JSON
{
    "return_order_id": 29,
    "data": [
        {
            "logistics_company": 65656,
            "logistics_freight_no": 1
        },
        {
            "logistics_company": 65656,
            "logistics_freight_no": 2
        },
        {
            "logistics_company": 65656,
            "logistics_freight_no": 24
        }
    ]
}

JSON
{
    "status_code": 400,
    "code": -1,
    "message": "货运单号已被占用"
}

SQL
select count(*) as aggregate from `return_orders` where `id` = 29
 
select
  count(*) as aggregate
from
  `return_order_logistics`
where
  `logistics_freight_no` = 1
  and `return_order_id` <> '29'

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系