6.4 Validaciones, Rutas Resource y Mensajes del Sistema

6.4.1 Validación de Datos

6.4.1.1 ¿Qué es la validación y por qué es necesaria?

La validación es el proceso mediante el cual se verifica que los datos enviados por el usuario cumplen con unas reglas antes de ser almacenados en la base de datos.

Laravel ofrece un sistema de validación muy robusto, que nos permite validar datos tanto desde el controlador como mediante clases personalizadas.

Importante

Siempre debemos validar los datos antes de almacenarlos y antes de actualizarlos. Nunca debemos asumir que lo que llega del formulario es seguro o correcto.

6.4.1.2 Validar desde el controlador usando $request->validate()

En este caso vamos a utilizar un checkbox para marcar si la nota está completada o no. En el formulario, si el checkbox está marcado, se enviará done=on, y si no está marcado, no se enviará nada. Para evitar problemas con el on vamos a modificar la vista de creación y edición de notas para que el valor enviado sea 1 o nada en el caso de no estar marcado.

1
<input type="checkbox" name="done" value="1">

Este método permite validar directamente dentro del método del controlador:

Función store()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public function store(Request $request)
{
    $validated = $request->validate([
        'title' => 'required|string|max:255',
        'description' => 'required|string|min:10',
        'date' => 'required|date',
        'done' => 'nullable|boolean'
    ]);

    Note::create($validated);
    return redirect()->route('note.index')->with('success', 'Nota creada correctamente.');
}

Cuando la validación falla, Laravel redirige automáticamente al formulario anterior. Una buena técnica es utilizar el método old() para recuperar el valor anterior del campos de manera que el usuario no tenga que volver a escribirlo.

1
<input name="title" value="{{ old('title') }}">

Haremos esto en todos los campos. Cuídado con los textarea que no tienen value y con los checkbox. Por ejemlo, para el checkbox:

1
<input type="checkbox" name="done" value="1" {{ old('done') ? 'checked' : '' }}>
Esto mostrará el checkbox marcado si el valor anterior era 1.

Reglas comunes de validación:

Regla Descripción
required El campo es obligatorio
string Debe ser una cadena de texto
min:n Longitud mínima de caracteres
max:n Longitud máxima de caracteres
date Debe ser una fecha válida
boolean true/false, 0/1, "yes"/"no"
nullable El campo puede estar vacío

6.4.1.3 Crear una clase FormRequest personalizada

Comando:

php artisan make:request NoteRequest

Esto creará una clase en app/Http/Requests/NoteRequest.php

Método authorize()

Este método define si el usuario tiene permiso para hacer esta petición. Para este curso lo dejaremos en true:

1
2
3
4
public function authorize()
{
    return true;
}

Método rules()

Define las reglas de validación:

Ejemplo de reglas

1
2
3
4
5
6
7
8
9
public function rules()
{
    return [
        'title' => 'required|string|max:255',
        'description' => 'required|string|min:10',
        'date' => 'required|date',
        'done' => 'nullable|boolean'
    ];
}

Uso en el controlador:

Almacenar una nota

1
2
3
4
5
6
7
public function store(NoteRequest $request)
{
    $data = $request->all();
    $data['done'] = $request->has('done') ? 1 : 0; // Convert checkbox to boolean
    Note::create($data);
    return redirect()->route('note.index');
}

Antes de modificar el update()recordar que en la vista de edición el checkbox se envía como done=on o no se envía nada. Hacemos en esta vista lo mismo que en la de creación:

1
<input type="checkbox" name="done" value="1" {{  ($note->done) ? 'checked' : '' }}>

Hacemos lo mismo para el método update():

Actualizar una nota

1
2
3
4
5
public function update(NoteRequest $request, Note $note)
{
    $note->update($request->all());
    return redirect()->route('note.index');
}

6.4.2 Mostrar Errores de Validación

6.4.2.1 Mostrar errores en el formulario

Puedes mostrar un error específico junto a cada campo:

1
2
3
4
<input name="title" value="{{ old('title') }}">
@error('title')
    <small style="color:red">{{ $message }}</small>
@enderror

6.4.2.2 Resaltar campos con error (CSS)

Puedes aplicar clases para marcar campos con error:

Resaltar campos con CSS
<input name="title" class="@error('title') is-invalid @enderror">

Y luego en CSS puedes estilizar .is-invalid.

6.4.2.3 Mostrar todos los errores juntos

Mostrar errores en la parte superior del formulario

1
2
3
4
5
6
7
8
9
@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

6.4.2.4 Modificar la vista de edición como lo hicimos en la de creación

también modificamos la vista del formulario de edición para mostrar los mensajes de error:

Vista de edición

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@extends('layouts.app')

@section('title', 'Editar Nota')

@section('content')
    <h2>Editar Nota</h2>
    <form action="{{ route('note.update', $note->id) }}" method="POST">
        @csrf
        @method('PUT')
        <label>Título:</label>
        <input type="text" name="title" value="{{ $note->title }}" required>
        @error('title')
            <small style="color:red">{{ $message }}</small>
        @enderror

        <label>Descripción:</label>
        <textarea name="description" required>{{ $note->description }}</textarea>
        @error('description')
            <small style="color:red">{{ $message }}</small>
        @enderror

        <label>Fecha:</label>
        <input type="date" name="date" value="{{ $note->date }}" required>
        @error('date')
            <small style="color:red">{{ $message }}</small>
        @enderror

        <label>Completada:</label>
        <input type="checkbox" name="done" {{ $note->done ? 'checked' : '' }}>

        <button type="submit">Actualizar</button>
        <a href="{{ route('note.index') }}">Cancelar</a>
        @error('done')
            <small style="color:red">{{ $message }}</small>
        @enderror
    </form>
@endsection

Algunos métodos interesantes de laravel que no hemos utilizado pero que se usan a menudo son:

Clase Método Descripción
Illuminate\Support\Facades\Validator make() Crear un validador
Illuminate\Support\Facades\Validator fails() Verificar si la validación falló
Illuminate\Support\Facades\Validator errors() Obtener los errores de validación
Illuminate\Support\Facades\Validator validate() Validar y redirigir automáticamente
Illuminate\Support\Facades\Request old() Obtener el valor anterior de un campo
Illuminate\Support\Facades\Request flash() Guardar datos en la sesión para la siguiente petición

6.4.2.4 Traducir los mensajes de error

Laravel trae sus mensajes de error por defecto en inglés.

  • Archivo por defecto: resources/lang/en/validation.php
  • Puedes crear una versión en español copiando el contenido en: resources/lang/es/validation.php
  • Activa el idioma por defecto en config/app.php:
'locale' => 'es',

6.4.3 Mensajes de Éxito

6.4.3.1 Flash de sesión con with()

Este método permite guardar un mensaje en la sesión que se mostrará en la siguiente petición. Vamos a usarlo para mostrar un mensaje de éxito después de crear o actualizar o eliminar una nota.

En la función store() del controlador:

return redirect()->route('note.index')->with('success', 'Nota guardada correctamente.');

En la función update():

return redirect()->route('note.index')->with('success', 'Nota actualizada correctamente.');
En la función destroy():

return redirect()->route('note.index')->with('danger', 'Nota eliminada correctamente.');

6.4.3.2 Mostrar el mensaje en la vista (por ejemplo, en layout):

Mostrar mensaje de éxito

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@if (session('success'))
    <div class="alert alert-success">
        {{ session('success') }}
    </div>
@endif
@if (session('danger'))
    <div class="alert alert-danger">
        {{ session('danger') }}
    </div>
@endif

O incluirlo como partial:

_partials/messages.blade.php

Partial para mensajes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@if (session('success'))
    <div class="alert alert-success" 
            style="padding: 10px; margin-bottom: 20px; 
                    background-color: #d4edda; 
                    color: #155724; 
                    border-color: #c3e6cb; 
                    border-radius: 5px;">
        {{ session('success') }}
    </div>
@endif
@if (session('danger'))
    <div class="alert alert-danger" 
        style="padding: 10px; margin-bottom: 20px; 
                background-color: #f8d7da; 
                color: #721c24; 
                border-color: #f5c6cb; 
                border-radius: 5px;">
        {{ session('danger') }}
    </div>
@endif

Y en el layout:

@include('_partials.messages')

6.4.4 Rutas Resource

6.4.4.1 ¿Qué son?

Las rutas Route::resource() generan automáticamente todas las rutas necesarias para un CRUD completo.

Route::resource('note', NoteController::class);

6.4.4.2 Acciones generadas

Ruta Método Acción
GET /note index Mostrar todas las notas
GET /note/create create Formulario para nueva nota
POST /note store Guardar nueva nota
GET /note/{note} show Mostrar una nota
GET /note/{note}/edit edit Formulario para editar
PUT/PATCH /note/{note} update Actualizar nota
DELETE /note/{note} destroy Eliminar nota

6.4.4.3 Personalización

  • Solo algunas rutas:
Route::resource('note', NoteController::class)->only(['index', 'show']);
  • Excluir algunas:
Route::resource('note', NoteController::class)->except(['destroy']);

6.4.4.4 Ver rutas disponibles

php artisan route:list

Te muestra todas las rutas definidas, su método, URI y nombre.

6.4.4.5 Crear un controlador tipo resource

php artisan make:controller NoteController --resource

Este comando crea todos los métodos básicos (index, create, store, show, edit, update, destroy).

6.4.5 Conclusiones

  • Validar datos es esencial para proteger la integridad de la base de datos.
  • Los mensajes de error y éxito mejoran la experiencia del usuario.
  • Las rutas resource simplifican la estructura del código.

Ejercicio para el Tema 9: Validaciones y Mensajes para Productos

Enunciado

Objetivo: Mejorar el CRUD de productos implementando validaciones, gestión de errores y mensajes de éxito.

  • Crea una clase FormRequest para productos:

  • Define las reglas de validación:

    • name: requerido, mínimo 3 caracteres.
    • description: requerido, mínimo 10 caracteres.
    • price: requerido, numérico, mayor que 0.
    • stock: requerido, entero, mínimo 0.
  • Utiliza StoreProductRequest en los métodos store() y update() del controlador.

  • Gestiona errores en los formularios:

  • Mostrar errores junto a los inputs con @error.
  • Mostrar una lista general de errores arriba del formulario si existen.

  • Mostrar mensaje de éxito:

  • Cuando un producto se cree, actualice o elimine correctamente.
  • Usa session()->flash('success', 'Producto creado correctamente.')
  • Muestra los mensajes en el layout usando un partial partials/alert.blade.php.

  • Usa el comando:

php artisan route:list
Para comprobar que las rutas del recurso de productos están correctamente creadas.


Ejercicio Tema: Solución

Ver Solución Tema 9

Ver Solución Tema 9
// Crear el FormRequest
php artisan make:request StoreProductRequest
// app/Http/Requests/StoreProductRequest.php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreProductRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'name' => 'required|string|min:3|max:255',
            'description' => 'required|string|min:10',
            'price' => 'required|numeric|min:0.01',
            'stock' => 'required|integer|min:0',
        ];
    }
}
// ProductController.php
use App\Http\Requests\StoreProductRequest;

public function store(StoreProductRequest $request)
{
    Product::create($request->validated());
    return redirect()->route('product.index')->with('success', 'Producto creado correctamente.');
}

public function update(StoreProductRequest $request, Product $product)
{
    $product->update($request->validated());
    return redirect()->route('product.index')->with('success', 'Producto actualizado correctamente.');
}

public function destroy(Product $product)
{
    $product->delete();
    return redirect()->route('product.index')->with('danger', 'Producto eliminado correctamente.');
}
<!-- resources/views/products/create.blade.php o edit.blade.php -->

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

@if (session('success'))
    <div class="alert alert-success">
        {{ session('success') }}
    </div>
@endif
@if (session('danger'))
    <div class="alert alert-success">
        {{ session('danger') }}
    </div>
@endif
// Verificar rutas
php artisan route:list