Link Search Menu Expand Document

5.17. CRUD desarrollado con Laravel: un ejemplo completo

Para finalizar esta introducción (sí, sí: aunque haya sido muy largo, no es más que una introducción) a Laravel, vamos a desarrollar una pequeña aplicación web desde cero.

Se tratará, en realidad, de un fragmento de otra aplicación más grande: una tienda online o tal vez un sistema de gestión de almacén. Da lo mismo. Nosotros vamos a desarrollar la parte de mantenimiento de productos.

Para ello, supondremos que en la base de datos existe una tabla llamada products con los campos id, name, description y price.

Vamos a construir el controlador, el modelo y las vistas necesarias para hacer el CRUD completo (create-read-update-delete) de esta tabla con Laravel, sin olvidarnos de las migraciones, los seeders y, por supuesto, el enrutador.

¿Estás preparado/a? Pues vamos allá.

Migraciones

Para esta miniaplicación solo necesitamos una migración, puesto que solo tenemos que crear una tabla.

La migración se crea con el comando php artisan make:migration create_table_products y se escribe en el archivo /database/migrations/_timestamp_create_products_table.php, que tendrá este contenido:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateProductsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name', 100);
            $table->string('description', 500);
            $table->double('price', 5, 2);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('products');
    }
}

Seeders

En este seeder vamos a cargar unos cuantos datos de prueba. Obviamente, puedes cambiarlos por los que tú quieras.

El seeder se crea con el comando php artisan make:seeder ProductTableSeeder, que generará el archivo /database/seeders/ProductTableSeeder.php.

Recuerda que, para poder lanzar el seeder automáticamente con php artisan migrate:fresh --seed u otro comando similar, tienes que editar el archivo DatabaseSeeder.php y añadir la línea $this->call(ProductTableSeeder::class); al método run().

En cualquier caso, siempre puedes lanzar el seeder manualmente en cualquier momento con php artisan db:seed --class=ProductTableSeeder.

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use DB;

class ProductTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('products')->insert([
            'name' => 'Papel higiénico',
            'description' => 'Papel higiénico extrasuave para glúteos sensibles',
            'price' => '1.35',
        ]);
        DB::table('products')->insert([
            'name' => 'Lejía blanca 1L',
            'description' => 'Lejía pura capaz de traladrar las baldosas',
            'price' => '0.95',
        ]);
        DB::table('products')->insert([
            'name' => 'Detergente Ariel 1.5L',
            'description' => 'Detergente líquido para lavadoras',
            'price' => '4.90',
        ]);
        DB::table('products')->insert([
            'name' => 'Gel Hidroalcohólico 350ml',
            'description' => 'Gel hidroalcólico sin perfume, 70% de alcohol',
            'price' => '3.50',
        ]);
        DB::table('products')->insert([
            'name' => 'Gel de baño con avena - 750 ml',
            'description' => 'Gel de baño con aceites vegetales, hipoalergénico',
            'price' => '1.50',
        ]);
    }
}

Enrutador

El enrutador de la aplicación está en /routes/web.php.

Basta con abrirlo y añadir esta línea:

Route::resource('products', 'ProductController');

Alternativamente, podrías crear a mano las siete entradas correspondientes a las siete rutas de un servidor REST, así:

Route::get('product', 'ProductController@index')->name('product.index');
Route::get('product/{product}', 'ProductController@show')->name('product.show');
Route::get('product/create', 'ProductController@create')->name('product.create');
Route::post('product/{product}', 'ProductController@store')->name('product.store');
Route::get('product/{product}/edit', 'ProductController@edit')->name('product.edit');
Route::patch('product/{product}', 'ProductController@update')->name('product.update');
Route::delete('product/{product}', 'ProductController@destroy')->name('product.destroy');

El resultado sería el mismo, pero si defines manualmente las rutas, tienes más control sobre cómo son exactamente. Por ejemplo, puedes traducirlas a español (¿qué tal cambiar “product/create” por “producto/crear”?).

O podrías hacer algún cambio más profundo a nivel técnico. Por ejemplo, que la petición para hacer delete llegue por GET en lugar de por DELETE (así no tendrías que usar un botón de formulario para lanzar el borrado de un producto y podrías lanzarlo con un link).

Eso sí: ten en cuenta que, si haces algún cambio de este tipo en las rutas, tu servidor ya no será 100% REST.

Hay una posibilidad intermedia: respetar las 7 rutas estándar REST y añadir alguna adicional que te venga bien, como el borrado mediante GET. Algo así:

// Estas son las 7 rutas estándar REST:
Route::resource('products', 'ProductController');
// Añadimos una ruta NO ESTÁNDAR para borrar productos mediante GET
Route::get('product/delete/{product}', 'ProductController@destroy')->name('product.myDestroy');

Controlador

El controlador de productos se crea con el comando php artisan make:controller ProductController.

El archivo se generará en /app/Http/Controllers/ProductController.php. Tendrás que rellenar el código de los 7 métodos REST con algo como esto::

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Product;

class ProductController extends Controller
{
    public function index() {
        $productList = Product::all();
        return view('product.all', ['productList'=>$productList]);
    }

    public function show($id) {
        $p = Product::find($id);
        $data['product'] = $p;
        return view('product.show', $data);
    }

    public function create() {
        return view('product.form');
    }

    public function store(Request $r) {
        $p = new Product();
        $p->name = $r->name;
        $p->description = $r->description;
        $p->price = $r->price;
        $p->save();
        return redirect()->route('product.index');
    }

    public function edit($id) {
        $product = Product::find($id);
        return view('product.form', array('product' => $product));
    }

    public function update($id, Request $r) {
        $p = Product::find($id);
        $p->name = $r->name;
        $p->description = $r->description;
        $p->price = $r->price;
        $p->save();
        return redirect()->route('product.index');
    }

    public function destroy($id) {
        $p = Product::find($id);
        $p->delete();
        return redirect()->route('product.index');
    }
}

Modelo

El modelo se crea con el comando php artisan make:model Product.

El archivo con el modelo se generará en app/models/product.php.

No es necesario que toques este archivo: puedes dejarlo, de momento, tal y como lo ha generado Artisan.

Vista: plantilla principal

La plantilla principal o master layout debes crearla en views/layouts/master.blade.php.

Por supuesto, puedes hacerla como quieras. Aquí te propongo un master layout muy sencillito al que luego le podrás ir añadiendo cosas fácilmente:

  <html>
     <head>
        <title>@yield('title')</title>
     </head>
     <body>
        @section('sidebar')
            Este es mi master sidebar.
        @show

        <div class="container">
            @yield('content')
        </div>
     </body>
  </html>

Vista: todos los productos

La vista con todos los productos la llamaremos views/products/all.blade.php y puede tener un aspecto como este:


@extends("layouts.master")

@section("title", "Administración de productos")

@section("header", "Administración de productos")

@section("content")
    <a href="{{ route('product.create') }}">Nuevo</a>
    <table border='1'>
    @foreach ($productList as $product)
        <tr>
            <td>{{$product->name}}</td>
            <td>{{$product->description}}</td>
            <td>{{$product->price}}</td>
            <td>
                <a href="{{route('product.edit', $product->id)}}">Modificar</a></td>
            <td>
                <form action = "{{route('product.destroy', $product->id)}}" method="POST">
                    @csrf
                    @method("DELETE")
                    <input type="submit" value="Borrar">
                </form>
            </td>
        <br>
    @endforeach
    </table>
@endsection

Vista: creación/modificación de productos

Reutilizaremos la vista para crear y modificar productos, puesto que son prácticamente iguales.

El archivo de la vista será views/products/form.blade.php, y su contenido puede ser algo así:


@extends("layouts.master")

@section("title", "Inserción de productos")

@section("header", "Inserción de productos")

@section("content")
    @isset($product)
        <form action="{{ route('product.update', ['product' => $product->id]) }}" method="POST">
        @method("PUT")
    @else
        <form action="{{ route('product.store') }}" method="POST">
    @endisset
        @csrf
        Nombre del producto:<input type="text" name="name" value="{{$product->name ?? '' }}"><br>
        Descripción:<input type="text" name="description" value="{{$product->description ?? '' }}"><br>
        Precio:<input type="text" name="price" value="{{$product->price ?? '' }}"><br>
        <input type="submit">
        </form>
@endsection

Observa cómo se genera una cabecera de formulario distinta según se vaya a usar el formulario para crear o para modificar un producto. Asímismo, fíjate en cómo se rellenan los atributos value de los campos del formulario con los datos actuales del producto (en caso de que existan).

¿Y ahora qué?

Ahora ha llegado el momento de comprobar si tu aplicación funciona.

Primero, lanza las migraciones y los seeders con php artisan migrate:fresh --seed. Asegúrate de haber añadido tu seeder de productos a DatabaseSeeder.php para que se lance automáticamente tras las migraciones. Si todo va bien, la aplicación estará lista para responder en https://tu-servidor-local/product

El código que hemos mostrado hasta aquí solo es un pequeño ejemplo y se puede mejorar de muchísimas maneras, por supuesto. Estas son algunas mejoras evidentes:

  • Programar la vista product/show.blade.php, que está ausente en el código anterior. Esa es la vista que mostrará un producto individual. Si intentas lanzarla ahora pidiendo la ruta https://tu-servidor/product/1 (sustituye 1 por el ID de cualquier producto), verás que Laravel te da un error de “View not found”.
  • Se puede simplificar el método store() del controlador haciendo una asignación masiva. Bastaría con indicar en el modelo que todos los campos de la tabla son fillables para poder hacer en el método store() del controlador algo como esto: $p = new Product($r->all())
  • Se puede alterar el aspecto visual de la aplicación trabajando el master layout y añadiendo ahí algo de CSS y/o Javascript, sin que haya que tocar el resto de vistas.
  • A partir de ahí, habría que seguir construyendo la aplicación, añadiendo más tablas con su correspondientes controladores, modelos y vistas. Llegará un momento en el que tendremos que crear las relaciones entre las tablas en los modelos, como explicamos en el apartado dedicado a Eloquent.
  • Otro paso lógico, común a muchas aplicaciones web, sería añadir un sistema de autenticación con Laravel Breeze.

Utiliza este código fuente como punto de partida para tus propios desarrollos con Laravel. Cuando hayas cogido un poco de práctica, verás que resulta mucho más rápido montar una aplicación web convencional con Laravel que hacerlo con PHP clásico.