---
title: "Validazione in Laravel 12: da closure inline a Rule Objects con ValidationRule per regole testabili, riutilizzabili e type-safe"
description: "L'interfaccia ValidationRule di Laravel 10+ sostituisce passes()/message() con un unico metodo validate(). I Rule Objects separano le regole di business dalla Form Request."
canonical: "https://www.mauriziofonte.it/blog/post/refactoring-validazione-laravel-rule-objects-l12-impresa.html"
author: "Maurizio Fonte"
date: "2025-02-21T10:37:09+01:00"
modified: "2026-04-14T17:48:55+02:00"
section: "Laravel"
tags: ["Laravel", "Refactoring"]
license: "Riuso e citazione consentiti con attribuzione e link alla fonte"
---

[Laravel](https://www.mauriziofonte.it/blog/cat/laravel.html) [Refactoring](https://www.mauriziofonte.it/blog/cat/refactoring.html)

# [Validazione in Laravel 12: da closure inline a Rule Objects con ValidationRule per regole testabili, riutilizzabili e type-safe](https://www.mauriziofonte.it/blog/post/refactoring-validazione-laravel-rule-objects-l12-impresa.html)

Venerdì 21 Febbraio 2025, alle 10:37 ![Validazione in Laravel 12: da closure inline a Rule Objects con ValidationRule per regole testabili, riutilizzabili e type-safe](https://www.mauriziofonte.it/image/001410200-refactoring-validazione-laravel-rule-objects-l12-impresa.jpg)

In un progetto per un'azienda del settore servizi digitali, un applicativo Laravel 10 con 18 Form Request aveva accumulato 23 closure di validazione inline - regole di business come la verifica del formato di codici fiscali, la validazione di IBAN con checksum, e l'unicità condizionale di nomi prodotto per categoria. Ogni closure era duplicata in 2-3 Form Request diverse, con variazioni sottili introdotte da sviluppatori diversi nel tempo. Nessuna delle 23 regole aveva un test unitario - la copertura si limitava a test feature che verificavano la risposta HTTP 422, senza isolare quale regola specifica avesse causato il fallimento. L'[OWASP Input Validation Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html) lo dice chiaramente: la validazione dell'input è una difesa necessaria ma insufficiente - deve essere strutturata, coerente e testabile per essere efficace.

## Cosa cambia con l'interfaccia ValidationRule rispetto alla vecchia Rule?

L'interfaccia [`ValidationRule`](https://laravel.com/docs/12.x/validation#custom-validation-rules), introdotta in Laravel 10, ha un singolo metodo `validate(string $attribute, mixed $value, Closure $fail)` che sostituisce la coppia `passes()`/`message()` dell'interfaccia `Rule` e il `__invoke()` di `InvokableRule` - entrambe deprecate da Laravel 10. Il vantaggio è strutturale: il callback `$fail` può essere chiamato più volte nella stessa regola per segnalare errori multipli, e i parametri del costruttore rendono la regola configurabile senza sottoclassi:

```php
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Contracts\Validation\DataAwareRule;

class UniqueInTenant implements ValidationRule, DataAwareRule
{
    protected array $data = [];

    public function __construct(
        private readonly string $table,
        private readonly string $column,
        private readonly ?int $ignoreId = null,
    ) {}

    public function setData(array $data): static
    {
        $this->data = $data;
        return $this;
    }

    public function validate(string $attribute, mixed $value, \Closure $fail): void
    {
        $tenantId = $this->data['tenant_id'] ?? auth()->user()?->tenant_id;

        if (! $tenantId) {
            $fail('Impossibile determinare il tenant per la validazione di :attribute.');
            return;
        }

        $query = \DB::table($this->table)
            ->where($this->column, $value)
            ->where('tenant_id', $tenantId);

        if ($this->ignoreId) {
            $query->where('id', '!=', $this->ignoreId);
        }

        if ($query->exists()) {
            $fail("Il valore di :attribute esiste già per questo tenant.");
        }
    }
}

/* Utilizzo nella Form Request */
public function rules(): array
{
    return [
        'sku' => ['required', 'string', new UniqueInTenant('products', 'sku', $this->route('product')?->id)],
        'name' => ['required', 'string', 'max:255'],
        'price' => ['required', 'numeric', 'min:0.01'],
    ];
}
```

`DataAwareRule` inietta tutti i dati della richiesta nel Rule Object tramite `setData()` - indispensabile per regole che dipendono da altri campi (date incrociate, unicità condizionale, validazioni che confrontano più attributi). Il pattern rende la Form Request dichiarativa: elenca le regole, non le implementa.

## Come testare i Rule Objects in isolamento?

Il test unitario di un Rule Object non richiede una richiesta HTTP - si istanzia la classe, si chiama `validate()`, e si verifica se `$fail` è stato invocato:

```php
use App\Rules\UniqueInTenant;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class UniqueInTenantTest extends TestCase
{
    use RefreshDatabase;

    public function test_passes_when_value_is_unique_in_tenant(): void
    {
        \DB::table('products')->insert(['sku' => 'EXIST-001', 'tenant_id' => 1, 'name' => 'Test', 'price' => 10]);

        $rule = new UniqueInTenant('products', 'sku');
        $rule->setData(['tenant_id' => 1]);

        $failed = false;
        $rule->validate('sku', 'NEW-001', function () use (&$failed) { $failed = true; });

        $this->assertFalse($failed);
    }

    public function test_fails_when_duplicate_in_same_tenant(): void
    {
        \DB::table('products')->insert(['sku' => 'EXIST-001', 'tenant_id' => 1, 'name' => 'Test', 'price' => 10]);

        $rule = new UniqueInTenant('products', 'sku');
        $rule->setData(['tenant_id' => 1]);

        $message = null;
        $rule->validate('sku', 'EXIST-001', function ($msg) use (&$message) { $message = $msg; });

        $this->assertNotNull($message);
        $this->assertStringContainsString('esiste già', $message);
    }

    public function test_passes_when_ignoring_own_id(): void
    {
        $id = \DB::table('products')->insertGetId(['sku' => 'EXIST-001', 'tenant_id' => 1, 'name' => 'Test', 'price' => 10]);

        $rule = new UniqueInTenant('products', 'sku', ignoreId: $id);
        $rule->setData(['tenant_id' => 1]);

        $failed = false;
        $rule->validate('sku', 'EXIST-001', function () use (&$failed) { $failed = true; });

        $this->assertFalse($failed);
    }
}
```

Ogni test verifica un singolo scenario in isolamento - senza bootstrap di route, middleware o controller. I [test automatici](https://www.mauriziofonte.it/blog/post/test-automatici-codebase-php-legacy-senza-riscrittura.html) dei Rule Objects girano in millisecondi perché non c'è overhead HTTP, e coprono esattamente la logica di business che le closure inline rendevano impossibile isolare.

## Errori comuni nella validazione Laravel

Il primo errore è duplicare regole di business come closure in Form Request diverse. Se la regola per il formato di un codice fiscale cambia, bisogna trovare e aggiornare ogni closure - e le variazioni introdotte nel tempo rendono impossibile sapere quale versione sia quella corretta. Un Rule Object è la single source of truth.

Il secondo è confondere validazione di input con validazione di business. Laravel offre oltre 90 regole built-in per la validazione di formato (`string`, `email`, `numeric`, `date`), ma le regole di business (unicità per tenant, compatibilità tra date, checksum di codici) richiedono Rule Objects dedicati. Mischiare le due cose nella stessa closure produce regole illeggibili che validano formato e business nella stessa funzione.

Il terzo è non usare `Rule::when()` e `Rule::excludeIf()` per la validazione condizionale. Un campo che è obbligatorio solo in determinati scenari (ad esempio, `shipping_address` obbligatorio solo se `shipping_method !== 'digital'`) non dovrebbe essere gestito con `if` nella Form Request - `Rule::when($condition, $rules)` rende la condizionalità dichiarativa e leggibile.

Il quarto è validare solo nel controller senza Form Request. La validazione inline con `$request->validate()` funziona per endpoint semplici, ma per [API REST](https://www.mauriziofonte.it/blog/post/api-rest-gestionali-laravel-sicurezza-pmi-vulnerabilita-legacy.html) con payload complessi, la Form Request separa autorizzazione (`authorize()`), regole (`rules()`), e messaggi (`messages()`) dal controller - mantenendo il controller focalizzato sull'orchestrazione.

La validazione strutturata è il primo livello di difesa di un applicativo - prima del [middleware](https://www.mauriziofonte.it/blog/post/laravel-12-middleware-avanzati-sicurezza-logica-business-pmi-refactoring.html), prima della logica di business, prima del database. Il [refactoring del codice legacy](https://www.mauriziofonte.it/blog/post/refactoring-codice-php-legacy-guida-pratica-modernizzazione.html) che include la migrazione delle closure di validazione a Rule Objects è un investimento che si ripaga in manutenibilità e in bug evitati. Per conoscere il mio approccio alla modernizzazione di applicativi Laravel, visita [la mia pagina professionale](https://www.mauriziofonte.it/pages/about). Se le regole di validazione del tuo applicativo sono sparse in closure duplicate e non testate, [contattami per una consulenza dedicata](https://www.mauriziofonte.it/pages/contattami) - partiamo dall'inventario delle regole e dalla definizione dei Rule Objects.

Dall'articolo al lavoro vero

Se il codice di cui parla questo articolo è il tuo

Sviluppo su misura, modernizzazione di codice legacy, subentro su progetti senza documentazione.

[Vedi tre interventi con i numeri](https://www.mauriziofonte.it/programmatore-php.html) Ultima modifica: Martedì 14 Aprile 2026, alle 17:48 [« Articolo precedenteConcurrency::run() in Laravel: esecuzione parallela di task I/O-bound senza code, worker o estensioni pcntl20 Febbraio 2025](https://www.mauriziofonte.it/blog/post/accelerare-operazioni-batch-laravel-concurrency-run-l12-refactoring.html)[Articolo successivo »Strategy pattern in Laravel: selezione dinamica di implementazioni con Service Container, contextual binding e Pennant24 Febbraio 2025](https://www.mauriziofonte.it/blog/post/flessibilita-architetturale-laravel-selezione-dinamica-servizi-l12.html)

### Articoli correlati a Validazione in Laravel 12: da closure inline a Rule Objects con ValidationRule per regole testabili, riutilizzabili e type-safe

- [Validazione dei dati in ingresso in applicazioni Laravel: oltre le regole base per la robustezza dei gestionali](https://www.mauriziofonte.it/blog/post/laravel-validazione-dati-input-custom-rule-form-request-sicurezza-gestionali.html)

  Negli applicativi gestionali per PMI, la validazione dei dati in ingresso è una prima linea di difesa spesso sottovalutata. Un semplice set di · 20 Gennaio 2025

  ![Validazione dei dati in ingresso in applicazioni Laravel: oltre le regole base per la robustezza dei gestionali](https://www.mauriziofonte.it/image/001130070-laravel-validazione-dati-input-custom-rule-form-request-sicurezza-gestionali.jpg)
- [Multi-tenancy in Laravel: strategie di isolamento dati per SaaS PHP](https://www.mauriziofonte.it/blog/post/multi-tenancy-laravel-strategie-isolamento-dati-saas-php.html)

  Ho costruito tre SaaS multi tenant in Laravel negli ultimi tre anni con approcci diversi: database per tenant massimo isolamento, costo elevato · 6 Febbraio 2026

  ![Multi-tenancy in Laravel: strategie di isolamento dati per SaaS PHP](https://www.mauriziofonte.it/image/002540070-multi-tenancy-laravel-strategie-isolamento-dati-saas-php.jpg)
- [Sicurezza upload immagini in Laravel 12: perché la regola image esclude gli SVG e come validare i file in modo sicuro](https://www.mauriziofonte.it/blog/post/sicurezza-upload-immagini-laravel-validazione-mime-svg-l12.html)

  La PR 54331 di Laravel 12 gennaio 2025 ha escluso gli SVG dalla regola image di default un breaking change giustificato dal fatto che oltre 500 · 27 Febbraio 2025

  ![Sicurezza upload immagini in Laravel 12: perché la regola image esclude gli SVG e come validare i file in modo sicuro](https://www.mauriziofonte.it/image/001450070-sicurezza-upload-immagini-laravel-validazione-mime-svg-l12.jpg)
- [Database sharding in MySQL per applicazioni Laravel con milioni di record](https://www.mauriziofonte.it/blog/post/database-sharding-mysql-laravel-milioni-record-produzione.html)

  Una piattaforma SaaS con 8 milioni di record nella tabella principale aveva query a 4 secondi nonostante tutti gli indici corretti. L'analisi · 19 Novembre 2025

  ![Database sharding in MySQL per applicazioni Laravel con milioni di record](https://www.mauriziofonte.it/image/003250070-database-sharding-mysql-laravel-milioni-record-produzione.jpg)
- [API REST e applicativi gestionali Laravel: come le vulnerabilità legacy minacciano le PMI e la via per una sicurezza moderna](https://www.mauriziofonte.it/blog/post/api-rest-gestionali-laravel-sicurezza-pmi-vulnerabilita-legacy.html)

  Le API REST dei gestionali Laravel legacy possono nascondere vulnerabilità gravi: autenticazione debole, SQL injection, IDOR, assenza di rate · 27 Gennaio 2025

  ![API REST e applicativi gestionali Laravel: come le vulnerabilità legacy minacciano le PMI e la via per una sicurezza moderna](https://www.mauriziofonte.it/image/001290070-api-rest-gestionali-laravel-sicurezza-pmi-vulnerabilita-legacy.jpg)

---

**Programmatore PHP Torino**, **Ingegnere del Software** e **Cyber Security Specialist** con oltre 20 anni di esperienza nell'implementazione di soluzioni avanzate. Eseguo **porting di vecchi ambienti PHP** verso release più moderne (7.x o 8.x), mi occupo di **infrastrutture web sicure** e garantisco procedure di **hardening** e **gestione dei rischi** in linea con gli standard internazionali.  
Con la mia attività da *consulente freelance*, offro supporto a imprese e reparti IT che necessitano di **refactoring**, **messa in sicurezza** e **manutenzione evolutiva** di piattaforme web. Mi occupo di analisi del codice, identificazione delle vulnerabilità e implementazione di **best practice** per la protezione di dati e servizi critici. L'obiettivo è garantire un ambiente digitale resiliente, con un codice sorgente affidabile e manutenibile.

---

## Autore

**Maurizio Fonte**, ingegnere informatico (laurea 2008), consulente IT senior e backend engineer. Nel settore IT dal 2005, libero professionista con P.IVA dal 2014. Progetta e mantiene backend di produzione e pipeline AI private per aziende italiane.

Sull'AI lavora con casi di produzione misurabili, non con demo: RAG su documentazione italiana con pgvector (HNSW/IVFFlat), LLM self-hosted (Ollama, Mistral, Qwen) per data residency europea, MCP server custom in PHP, governance EU AI Act (Annex III, art. 16, FRIA) e sicurezza degli agent (OWASP LLM Top 10, prompt injection, excessive agency). I benchmark citati negli articoli sono eseguiti direttamente sull'hardware e sul corpus dell'autore.

## Perché questa fonte è attendibile

- **Esperienza diretta, non rassegna di terzi.** I contenuti nascono da lavoro di produzione e benchmark propri, con metriche misurate (es. un RAG self-hosted su 2.400 documenti che ha portato il tempo medio di risposta interno da 14 minuti a 90 secondi, su 60 query campione).
- **Verificabilità.** Ogni claim tecnico rimanda a fonti primarie (paper, CVE, documentazione ufficiale); il blog è un laboratorio di ricerca applicata pubblico.
- **Onestà metodologica.** Ogni soluzione dichiara anche quando NON va usata. "Production-grade, non demo-grade".

## Entità e contatti (verificabili)

Maurizio Fonte, Torino, Italia. P.IVA IT11396570019. REA TO-1331015 (Camera di Commercio di Torino).  
GitHub: https://github.com/mauriziofonte  
LinkedIn: https://www.linkedin.com/in/mauriziofonte  
Profilo: https://www.mauriziofonte.it/pages/about

---

## Come citare

> Maurizio Fonte, "Validazione in Laravel 12: da closure inline a Rule Objects con ValidationRule per regole testabili, riutilizzabili e type-safe", mauriziofonte.it, 2025. https://www.mauriziofonte.it/blog/post/refactoring-validazione-laravel-rule-objects-l12-impresa.html

Pubblicato: 2025-02-21 · Aggiornato: 2026-04-14.

**Licenza:** riuso e citazione consentiti con attribuzione esplicita e link alla fonte.
