定制 fridzema/laravel-validation-plus 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

fridzema/laravel-validation-plus

最新稳定版本:v1.3.4

Composer 安装命令:

composer require fridzema/laravel-validation-plus

包简介

Non-blocking validation warnings for Laravel

README 文档

README

CI Coverage PHPStan Latest Version on Packagist PHP Version License

Non-blocking validation warnings for Laravel. Add advisory messages to your form requests that inform users without preventing submission.

How It Works

Warnings are advisory messages that don't block form submission. Unlike validation errors (red, HTTP 422), warnings (amber) let the request through while informing users about potential issues.

Requirements

Package PHP Laravel
1.x ^8.3 11.x, 12.x

Installation

composer require fridzema/laravel-validation-plus

Optionally publish the config:

php artisan vendor:publish --tag="validation-plus-config"

Add the middleware to routes that need warnings:

Route::middleware('warnings')->group(function () {
    // your routes
});

Usage

FormRequest

Add the HasWarningRules trait and define warningRules():

use Fridzema\ValidationPlus\Traits\HasWarningRules;
use Illuminate\Foundation\Http\FormRequest;

class StoreUserRequest extends FormRequest
{
    use HasWarningRules;

    public function rules(): array
    {
        return [
            'email' => ['required', 'email'],
            'name' => ['required', 'string'],
        ];
    }

    public function warningRules(): array
    {
        return [
            'name' => ['min:3'],
        ];
    }

    public function warningMessages(): array
    {
        return [
            'name.min' => 'Short names may cause display issues.',
        ];
    }

    public function warningAttributes(): array
    {
        return [
            'name' => 'display name',
        ];
    }
}

Warning rules are evaluated after standard validation passes. If validation fails, warnings are never checked.

Method Purpose Default
warningRules() Rules that trigger warnings []
warningMessages() Custom warning messages []
warningAttributes() Custom field display names (:attribute substitution) []

Manual Usage

Use WarningValidator directly in controllers:

use Fridzema\ValidationPlus\WarningBag;
use Fridzema\ValidationPlus\WarningValidator;

$validator = app(WarningValidator::class);

$warnings = $validator->validate(
    $request->all(),
    ['name' => 'min:3'],
    ['name.min' => 'Short names may cause display issues.'],
);

// Merge into the scoped bag
app(WarningBag::class)->merge($warnings->getMessages());

Blade

A $warnings variable (a WarningBag instance) is automatically shared with all views:

@if($warnings->any())
    <div class="alert alert-warning">
        <ul>
            @foreach($warnings->all() as $warning)
                <li>{{ $warning }}</li>
            @endforeach
        </ul>
    </div>
@endif

Or use the included component:

<x-validation-plus::warnings />

Or use the @warning directive (mirrors @error):

@warning('name')
    <span class="text-amber-600">{{ $message }}</span>
@endwarning

API Responses

When the ShareWarnings middleware is active and warnings exist, API responses automatically get:

  • An X-Validation-Warnings: true header
  • Warnings merged into the JSON body under a "warnings" key
{
    "status": "ok",
    "warnings": {
        "name": ["Short names may cause display issues."]
    }
}

Precognition (Real-Time Validation)

The package integrates with Laravel Precognition for real-time per-field warnings as users type.

Real-time warnings Real-time errors
Precognition Warnings Precognition Errors

Add the HandlePrecognitiveRequests middleware to your route:

Route::post('/profile', StoreProfileAction::class)
    ->middleware([HandlePrecognitiveRequests::class, 'warnings']);

Warning rules are automatically filtered by the Precognition-Validate-Only header, so only the field being validated is checked.

Warnings are returned in the X-Validation-Warnings-Data response header as JSON:

const response = await fetch('/profile', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Precognition': 'true',
        'Precognition-Validate-Only': 'name',
    },
    body: JSON.stringify(form),
});

if (response.status === 204) {
    const warnings = JSON.parse(
        response.headers.get('X-Validation-Warnings-Data') || '{}'
    );
}

Frontend Integration

Warnings travel in two ways depending on your setup:

Request type Warnings location
Standard API (Accept: application/json) JSON body under warnings key
Precognition (real-time, Precognition: true) X-Validation-Warnings-Data header as JSON

Axios

An interceptor that surfaces warnings on every response:

axios.interceptors.response.use((response) => {
    const header = response.headers['x-validation-warnings-data'];
    response.warnings = header ? JSON.parse(header) : (response.data?.warnings ?? {});
    return response;
});

// Usage
const response = await axios.post('/profile', form);
if (Object.keys(response.warnings).length) {
    console.log(response.warnings); // { name: ['Short names may cause display issues.'] }
}

Vue 3

// composables/useWarnings.js
import { ref } from 'vue';

export function useWarnings() {
    const warnings = ref({});

    function syncFromResponse(response) {
        const header = response.headers?.['x-validation-warnings-data'];
        warnings.value = header ? JSON.parse(header) : (response.data?.warnings ?? {});
    }

    const forField = (field) => warnings.value[field] ?? [];
    const hasWarning = (field) => forField(field).length > 0;
    const clear = () => { warnings.value = {}; };

    return { warnings, syncFromResponse, forField, hasWarning, clear };
}
<script setup>
import axios from 'axios';
import { reactive } from 'vue';
import { useWarnings } from '@/composables/useWarnings';

const form = reactive({ name: '' });
const { warnings, syncFromResponse, forField } = useWarnings();

async function submit() {
    const response = await axios.post('/profile', form);
    syncFromResponse(response);
}
</script>

<template>
    <form @submit.prevent="submit">
        <input v-model="form.name" />
        <p v-for="msg in forField('name')" class="text-amber-600">{{ msg }}</p>
        <button type="submit">Save</button>
    </form>
</template>

React

// hooks/useWarnings.js
import { useState, useCallback } from 'react';

export function useWarnings() {
    const [warnings, setWarnings] = useState({});

    const syncFromResponse = useCallback((response) => {
        const header = response.headers?.['x-validation-warnings-data'];
        setWarnings(header ? JSON.parse(header) : (response.data?.warnings ?? {}));
    }, []);

    const forField = (field) => warnings[field] ?? [];
    const hasWarning = (field) => forField(field).length > 0;
    const clear = () => setWarnings({});

    return { warnings, syncFromResponse, forField, hasWarning, clear };
}
import axios from 'axios';
import { useState } from 'react';
import { useWarnings } from './hooks/useWarnings';

export function ProfileForm() {
    const [name, setName] = useState('');
    const { syncFromResponse, forField } = useWarnings();

    async function handleSubmit(e) {
        e.preventDefault();
        const response = await axios.post('/profile', { name });
        syncFromResponse(response);
    }

    return (
        <form onSubmit={handleSubmit}>
            <input value={name} onChange={(e) => setName(e.target.value)} />
            {forField('name').map((msg, i) => (
                <p key={i} className="text-amber-600">{msg}</p>
            ))}
            <button type="submit">Save</button>
        </form>
    );
}

Alpine.js

<div x-data="warningForm()">
    <form @submit.prevent="submit">
        <input x-model="form.name" />
        <template x-for="msg in warnings.name ?? []">
            <p class="text-amber-600" x-text="msg"></p>
        </template>
        <button type="submit">Save</button>
    </form>
</div>

<script>
function warningForm() {
    return {
        form: { name: '' },
        warnings: {},
        async submit() {
            const response = await fetch('/profile', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
                body: JSON.stringify(this.form),
            });
            const data = await response.json();
            this.warnings = data.warnings ?? {};
        },
    };
}
</script>

Global Warnings

For advisory messages not tied to a specific field:

warnings()->addGlobal('Your account is approaching its storage limit.');
app(WarningBag::class)->addGlobal('Subscription expires in 3 days.');

Access them in Blade with the reserved __global__ key:

@warning('__global__')
    <div class="alert alert-info">{{ $message }}</div>
@endwarning

Helper Function

$bag = warnings(); // returns the scoped WarningBag

Testing

Test macros are registered automatically:

$response = $this->postJson('/api/users', [
    'email' => 'test@example.com',
    'name' => 'Jo',
]);

$response->assertOk();
$response->assertHasWarning('name');
$response->assertHasWarning('name', 'Short names may cause display issues.');
$response->assertHasNoWarnings('email');
$response->assertHasNoWarnings();                                           // no warnings at all
$response->assertWarnings(['name' => ['Short names may cause display issues.']]);  // exact shape

Configuration

return [
    // HTTP header added to API responses when warnings exist
    'header' => 'X-Validation-Warnings',

    // Merge warnings into JSON response body under the json_key
    'inject_json' => true,

    // JSON body key used when injecting warnings into API responses
    'json_key' => 'warnings',

    // Session key for flashing warnings on web requests
    'session_key' => 'warnings',
];

Warnings vs Errors

Errors Warnings
Block request Yes No
Cause validation failure Yes No
HTTP status 422 200 (original)
Blade variable $errors $warnings
Session flash Automatic Via middleware
API response Standard Laravel Header + JSON key

Octane Compatibility

WarningBag uses a scoped binding and is reset between requests automatically by Laravel Octane. No extra configuration needed.

Changelog

Please see CHANGELOG for more information on what has changed recently.

License

The MIT License (MIT). Please see License File for more information.

统计信息

  • 总下载量: 2
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 10
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-05-06

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固