6.3 Desarrollo de un CRUD Completo en Laravel (Parte 1)
6.3.1 Introducción a CRUD
El término CRUD corresponde a las operaciones básicas que se realizan en la mayoría de las aplicaciones que gestionan datos:
- Create (Crear): Insertar nuevos datos.
- Read (Leer): Consultar y visualizar datos.
- Update (Actualizar): Modificar datos existentes.
- Delete (Eliminar): Borrar datos.
En Laravel, la implementación de un CRUD completo nos permite comprender cómo los Modelos, Controladores y Vistas interactúan entre sí para ofrecer una experiencia de usuario completa.
Importante
Dominar el desarrollo de CRUDs es básico para cualquier programador web, ya que casi todas las aplicaciones web tienen que manejar datos de algún tipo.
6.3.2 Rutas Dinámicas y Controladores
6.3.2.1 Parámetros Dinámicos en Rutas
En Laravel, podemos definir rutas que aceptan parámetros dinámicos. Estos parámetros permiten que una misma ruta atienda solicitudes distintas según el valor proporcionado.
Ejemplo sencillo:
Route::get('/nota/{id}', function ($id) {
return "Mostrando la nota con ID: $id";
});
En este caso, id es un parámetro dinámico. Si accedemos a http://localhost:8080/nota/5, Laravel mostrará: "Mostrando la nota con ID: 5".
Múltiples parámetros:
Route::get('/usuario/{id}/nota/{nota_id}', function ($id, $nota_id) {
return "Usuario $id - Nota $nota_id";
});
Importante
El orden de los parámetros en la URL debe coincidir exactamente con el orden de los parámetros en la función anónima o el controlador.
6.3.2.2 Crear un Circuito MVC Rápido para Rutas Dinámicas
Modelo: Vamos a utilizar una tabla notes con los campos:
id(entero, autoincremental)title(string)description(text)date(date)done(boolean)
Vamos a crear la migración de la nota y el controador:
php artisan make:migration create_notes_table
php artisan make:model Note
Podemos crearla migración y la nota con un solo comando:
php artisan make:model Note -m
Ahora vamos al archivo de migración database/migrations/xxxx_xx_xx_create_notes_table.php y añadimos los campos:
Migración
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Por último, ejecutamos la migración:
php artisan migrate
Si tenemos algún problema porque no hemos creado la base de datos desde 0, podemos eliminar las migraciones anteiores con:
php artisan migrate:reset
Modelo Note:
Modelo
1 2 3 4 5 6 7 8 9 | |
$fillable: Define qué campos se pueden asignar en masa.$guarded: Define qué campos no se pueden asignar.
Controlador NoteController:
php artisan make:controller NoteController
Método para mostrar un ID:
1 2 3 4 | |
Ruta asociada:
Route::get('/note/{id}', [NoteController::class, 'show'])->name('note.show');
Vista resources/views/notes/show.blade.php:
<h1>Detalle de Nota</h1>
<p>El ID de la nota es: {{ $id }}</p>
Con esto, accediendo a /note/5 veremos "El ID de la nota es: 5".
Compact
La función compact('variable') crea un array asociativo ['variable' => $variable] que puede ser pasado a la vista. Es una forma rápida y limpia de pasar datos.
6.3.2.3 Parámetros Opcionales y Valores por Defecto
Podemos definir parámetros opcionales añadiendo un signo de interrogación ?:
1 2 3 | |
- Si accedemos a
/saludo/Laura, veremos "Hola, Laura". - Si accedemos a
/saludo, veremos "Hola, Invitado".
Notas importantes: - El parámetro opcional debe ser el último de la URL. - Hay que asignar un valor por defecto en la función.
6.3.2.4 Importancia del Orden de las Rutas
Laravel evalúa las rutas en el orden en que se definen.
Ejemplo de conflicto:
Route::get('/nota/nueva', function() { return 'Crear nueva nota'; });
Route::get('/nota/{id}', function($id) { return "Nota ID: $id"; });
- Primero debe definirse
/nota/nuevaporque si no, Laravel intentará interpretarnuevacomo unid. - El orden correcto es siempre de rutas más específicas a más generales.
Consejo
Primero define todas las rutas fijas y luego las rutas con parámetros dinámicos.
6.3.3 Desarrollo del CRUD para Notas
6.3.3.2 Listar Todas las Notas
Primero creamos la ruta y el método para listar todas las notas.
Ruta:
Route::get('/', [NoteController::class, 'index'])->name('note.index');
Controlador:
1 2 3 4 5 | |
Explicación de @forelse vs @foreach:
@foreachse utiliza para recorrer elementos, pero no gestiona si el array está vacío.@forelsepermite recorrer elementos y además definir qué hacer si no hay elementos.
Ejemplo:
| Listado de Notas | |
|---|---|
1 2 3 4 5 | |
Crear layout base en resources/views/layouts/app.blade.php:
Layout
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Vista de Listado resources/views/notes/index.blade.php:
Listado de Notas
@extends('layouts.app')
@section('title', 'Listado de Notas')
@section('content')
<h2>Listado de Notas</h2>
@forelse ($notes as $note)
<div>
<h3>{{ $note->title }}</h3>
<p>{{ $note->description }}</p>
<small>{{ $note->date }}</small>
<div>
<a href="{{ route('note.edit', $note->id) }}">Editar</a>
<form action="{{ route('note.destroy', $note->id) }}" method="POST" style="display:inline">
@csrf
@method('DELETE')
<button type="submit">Eliminar</button>
</form>
</div>
</div>
@empty
<p>No hay notas disponibles.</p>
@endforelse
@endsection
6.3.3.3 Crear una Nueva Nota
Ruta para formulario de creación:
Route::get('/note/create', [NoteController::class, 'create'])->name('note.create');
Controlador:
1 2 3 4 | |
Vista resources/views/notes/create.blade.php:
Formulario de Crear Nota
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
6.3.3.4 Guardar la Nueva Nota
Ruta para guardar:
Route::post('/note/store', [NoteController::class, 'store'])->name('note.store');
Controlador:
Para guardar la nota, podemos usar diferentes métodos. Aquí mostramos dos formas:
Guardar Nota
1 2 3 4 5 6 7 8 9 10 11 | |
O usando el método create:
1 2 3 4 5 | |
Explicaciones Adicionales:
@csrfprotege contra ataques CSRF (Cross-Site Request Forgery).$request->all()devuelve todos los datos enviados en el formulario.- Laravel valida automáticamente que el token CSRF esté presente. Si no lo está, lanzará un error.
¿Cómo funciona CSRF? - Laravel genera un token único para cada sesión de usuario. - Este token se incluye en cada formulario generado por Laravel. - Cuando se envía el formulario, Laravel verifica que el token enviado coincida con el de la sesión. - Si no coinciden, Laravel lanza un error 419 (Page Expired). - Esto previene que un atacante envíe formularios en nombre del usuario sin su consentimiento.
6.3.3.5 Editar una Nota
Ruta para formulario de edición:
Route::get('/note/edit/{note}', [NoteController::class, 'edit'])->name('note.edit');
Controlador:
Tenemos varias formas de recibir el parámetro note. En esta primer recibimos el ID y buscamos la nota, para poder pasarla a la vista:
1 2 3 4 5 | |
Note. De esta manera es Laravel el que se encarga de buscar la nota:
1 2 3 4 | |
Vista resources/views/notes/edit.blade.php:
En este caso la ruta la hemos definido con el método PUT. Este método es el que se utiliza para actualizar los datos de un recurso existente. Pero ¿cómo hacerlo si las opciones de form solo permiten GET y POST?. Laravel nos ofrece una solución sencilla: la directiva @method('PUT'). Esta directiva simula el método PUT en formularios HTML. Esta directiva debe estar dentro del formulario y antes de los inputs.
| Editar Nota | |
|---|---|
1 2 3 4 5 | |
Con este formato el formulario se enviará como un PUT, aunque el método del formulario sea POST.
Formulario de Editar Nota
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 | |
6.3.3.6 Actualizar la Nota
Ruta para actualizar:
Route::put('/note/update/{note}', [NoteController::class, 'update'])->name('note.update');
Controlador:
1 2 3 4 5 | |
@method('PUT')simula el método HTTP PUT en formularios HTML (que solo permiten GET y POST).
6.3.3.7 Mostrar una Nota Individual
Ruta para mostrar:
Route::get('/note/show/{note}', [NoteController::class, 'show'])->name('note.show');
Controlador:
1 2 3 4 | |
Vista resources/views/notes/show.blade.php actualizada:
Mostrar Nota
1 2 3 4 5 6 7 8 9 10 11 | |
6.3.3.8 Eliminar una Nota
Ruta para eliminar:
Route::delete('/note/destroy/{note}', [NoteController::class, 'destroy'])->name('note.destroy');
Controlador:
1 2 3 4 5 | |
- El método
delete()elimina el registro de la base de datos.
6.3.3.9 Tipado en los Métodos del Controlador
Ejemplo de tipado correcto:
1 2 3 4 5 | |
- Tipar los parámetros mejora la legibilidad y control de errores.
- Tipar el tipo de retorno ayuda a Laravel a validar internamente las respuestas.
Tipos comunes de retorno
Viewpara devolver vistas.RedirectResponsepara redirecciones.JsonResponsepara APIs.
Ejercicio para el Tema 8: CRUD de Productos
Enunciado
Objetivo: Crear un CRUD completo para gestionar productos en tu aplicación Laravel.
La tabla de productos debe tener los siguientes campos:
id(auto-incremental)name(string 255)description(text)price(decimal 8,2)stock(integer)timestamps
Pasos a seguir:
- Crea la migración para la tabla
products(si no existe) - Crea el modelo
Productsi no lo tienes ya, asegurándote de definir$fillable. - Crea el controlador
ProductControllercomo resource: - Define la rutas usando resource:
-
Crea las vistas en
resources/views/products/para:index.blade.php➔ Listar productoscreate.blade.php➔ Formulario de nuevo productoedit.blade.php➔ Formulario para editarshow.blade.php➔ Mostrar detalles del producto
-
Enlaza todas las acciones desde el listado (
index). - Usa layouts y secciones (
@section('title'),@section('content')).
Ejercicio Tema: Soluiones
Solución Tema 8
Ver Solución Tema 8
// routes/web.php
use App\Http\Controllers\ProductController;
Route::resource('product', ProductController::class);
// Crear el controlador
php artisan make:controller ProductController --resource
// Crear el modelo y la migración si no existe
php artisan make:model Product -m
// database/migrations/xxxx_create_products_table.php
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name', 255);
$table->text('description');
$table->decimal('price', 8, 2);
$table->integer('stock');
$table->timestamps();
});
}
// app/Models/Product.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = ['name', 'description', 'price', 'stock'];
}
<!-- resources/views/products/index.blade.php -->
@extends('layouts.app')
@section('title', 'Listado de Productos')
@section('content')
<h2>Productos</h2>
<a href="{{ route('product.create') }}">Crear Producto</a>
@forelse ($products as $product)
<div>
<h3>{{ $product->name }}</h3>
<p>{{ $product->description }}</p>
<p>Precio: ${{ $product->price }}</p>
<p>Stock: {{ $product->stock }}</p>
<a href="{{ route('product.edit', $product) }}">Editar</a>
<form action="{{ route('product.destroy', $product) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit">Eliminar</button>
</form>
</div>
@empty
<p>No hay productos disponibles.</p>
@endforelse
@endsection