Ir al contenido principal

Crear CRUD con Laravel

Hola, en esta ocasión les voy a mostrar como implementar un CRUD para crear, leer, actualizar y eliminar un producto utilizando el framework Laravel.

1. Crear el proyecto

Primeramente, vamos a crear un nuevo proyecto Laravel utilizando composer. Abrimos nuestra terminal y escribimos:
composer create-project --prefer-dist laravel/laravel blog

2. Base de datos

Necesitamos  configurar la conexión con nuestra base de datos. Par ello editamos el archivo .env que se encuentra en la raíz de nuestro proyecto
Abrimos el archivo .env ubicado en la raíz de nuestro proyecto y editamos las variables de conexión a nuestra base de datos:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=crud
DB_USERNAME=root
DB_PASSWORD=
Ahora crearemos nuestra tabla de “productos”. En nuestra terminal escribimos:
php artisan make:migration create_productos_table
Para establecer las columnas de nuestra tabla editemos el archivo que acabamos de generar
database\migrations\2020_05_26_045412_create_productos_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProductosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('productos'function (Blueprint $table) {
            $table->id();
            $table->string('nombre');
            $table->float('precio');
            $table->text('descripcion')->nullable();
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('productos');
    }
}
Bien, me parece necesario indicarte que la función $table->id() genera un campo de tipo entero autoincremental con el nombre id, lo establece como clave primaria. Y la funcion $table->timestamps() creará dos columnas (created_at, y updated_at) para guardar las fechas cada ves se crea o actualiza un nuevo registro a través de un modelo Eloquent.
Ahora migraremos nuestras tablas a nuestra base de datos con el siguiente comando:
 php artisan migrate:fresh

3. Modelo

En este punto vamos a generar nuestro modelo “Producto” con el siguiente comando:
 php artisan make:model Producto
Y procedemos a editar el archivo generado:
app\Producto.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Producto extends Model{
    protected $table = 'productos';
    protected $fillable = [ 'nombre''precio''descripcion' ];
}
Como puedes notar hemos usado la propiedad $table para indicarle a que tabla estará relacionado nuestro modelo. Además de estableces que propiedades serán de asignación masiva (lo explicare mas adelante) a través de la propiedad $fillable.

4. Controlador

Ahora vamos a crear nuestra controlador para la interacción de la vista con nuestro modelo. Ejecutamos el siguiente comando:
php artisan make:controller ProductoController --resource
 El comando anterior nos genera un controlador con todas las acciones necesarias para un “CRUD” ya solo queda implementar la lógica respectiva en cada función:
app\Http\Controllers\ProductoController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Producto;
class ProductoController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $productos = Producto::latest()->paginate(3);
        return view('productos.index'compact('productos') );
    }
    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('productos.form', [
            'action' => 'create',
            'producto'=> new Producto,
        ]);
    }
    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $request->validate([
            'nombre' => 'required|string',
            'precio' => 'required|numeric|gt:0',
        ]);
        Producto::create($request->all());
        return redirect('/productos')->with('success''Producto creado!');
    }
    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        return view('productos.form', [
            'action' => 'show',
            'producto'=> Producto::find($id),
        ]);
    }
    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        return view('productos.form', [
            'action' => 'edit',
            'producto'=> Producto::find($id),
        ]);
    }
    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $request->validate([
            'nombre' => 'required|string',
            'precio' => 'required|numeric|gt:0',
        ]);
        Producto::find($id)->update($request->all());
        return redirect('/productos')->with('success''Producto actualizado!');
    }
    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        Producto::find($id)->delete();
        return redirect('/productos')->with('success''Producto eliminado!');
    }
}

Index 

Este método obtiene el listado de los productos ordenados por fecha de creación descendente ( latest()) y paginados por cada 3 registros (paginate(3)). Luego redirecciona a la vista index con la lista de productos.

Create, Show, Edit

La lógica de estos estos 3 métodos es similar salvo que estamos enviado una variable $action para poder hacer algunas configuraciones en la vista del formulario y la variable $producto que recupera el registro de la base de datos (la función find() busca un registro por el id del producto). Solamente en la función create estamos enviando la variable $producto “vacía” (no olvides que es una instancia de Product::class pon ende  tiene propiedades y métodos).

Store

Este método valida los campos del formulario, y crea un nuevo producto en la base de datos a traves del método estático create(). Aquí sucede la magia, si notas una vez que la validación pasa podemos crear un registro a “ciegas”, las variables que vienen del formulario se asignan automáticamente a las propiedades del modelo (gracias a que hemos establecido dichas propiedad como asignables de forma masiva).

Update

Este método valida los campos del formulario, y modificar los datos del modelo para ello busca el modelo por id (find) y actualiza sus datos usando el método estático update().

Destroy 

Este método busca el registro en la base de datos y lo elimina con el método estático delete()
Nota: En estos tres últimos métodos redireccionamos al index, siempre con un mensaje respectivo por cada acción, este mensaje se asigna a la variable $success la cual se almacena solamente en la sesión actual.

5. Vistas

Previamente vamos a crear nuestro template para poder extender de esta plantilla. Te recomiendo que siempre crees uno, esto te servirá para diseñar el esqueleto de tu aplicación del cual dependerán todas nuestras vistas.
resources\views\layouts\master.blade.php
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ config('app.name''Laravel'}}</title>
    <link rel="stylesheet" href="{{ asset("css/bootstrap.min.css") }}">
    <script src="{{ asset('js/jquery.min.js') }}"></script>
    <script src="{{ asset('js/bootstrap.min.js') }}"></script>
</head>
<body>
    <div class="container my-3">
        @yield('content')
    </div>
</body>
</html>

 Para las vistas he creado dos scripts uno que es el index que muestra el listado de productos y otro el formulario. He considerado utilizar un único archivo de formulario para los métodos create, edit, show ya que muestran la misma información. Sin embargo, te recomiendo uses archivos separados por cada acción si estas implementando un formulario con muchos campos o campos dinámicos, de otra manera tu código podría volverse un dolor de cabeza.
resources\views\productos\index.blade.php
@extends('layouts.master')
@section('content')
<div class="row">
    {{-- Alerta --}}
    <div class="col-12">
        @if(session()->get('success'))
            <div class="alert alert-success alert-dismissible fade show" role="alert">
                {{ session()->get('success'}}
                <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
        @endif
    </div>
    {{-- Tabla --}}
    <div class="col-12">
        <div class="card">
            {{-- Titulo --}}
            <div class="card-header">
                <h5>Productos <a href="{{ url("/productos/create") }}" class="badge badge-pill badge-primary">Crear</a> </h5>
            </div>
            {{-- Lista --}}
            <div class="card-body p-0">
                <table class="table table-hover">
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Nombre</th>
                            <th>Precio</th>
                            <th>Descripcion</th>
                            <th width="200"></th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach ($productos as $producto)
                            <tr>
                                <td>{{ $producto->id }}</td>
                                <td>{{ $producto->nombre }}</td>
                                <td>{{ $producto->precio }}</td>
                                <td>{{ $producto->descripcion }}</td>
                                <td class="text-center">
                                    <a href='{{ url("/productos/$producto->id") }}' class="btn btn-sm btn-outline-info">Ver</a>
                                    <a href='{{ url("/productos/$producto->id/edit") }}' class="btn btn-sm btn-outline-success">Editar</a>
                                    <a href="#" onclick="event.preventDefault(); document.getElementById('form-delete-{{$producto->id}}').submit();" class="btn btn-sm btn-outline-danger">Eliminar</a>
                                    <form action="{{ url("/productos/$producto->id") }}" method="POST" id="form-delete-{{$producto->id}}">
                                        @csrf
                                        <input type="hidden" name="_method" value="DELETE">
                                    </form>
                                </td>
                            </tr>
                        @endforeach
                    </tbody>
                </table>
            </div>
            {{-- Paginacion --}}
            <div class="card-footer pb-0">
                {{ $productos->links() }}
            </div>
        </div>
    </div>
</div>
@endsection
resources\views\productos\form.blade.php
@extends('layouts.master')
@section('content')
    <div class="row d-flex justify-content-center">
        <div class="col-md-8 col-lg-6">
            {{-- Formulario --}}
            @if$action=='create' ) 
            <form action="{{ url("/productos") }}" method="POST" id="form-crud">
            @elseif$action=='edit' ) 
            <form action="{{ url("/productos/$producto->id") }}" method="POST" id="form-crud">
            <input type="hidden" name="_method" value="PUT">
            
            @endif
                <div class="card">
                    <div class="card-header">
                        <h5>Producto</h5>
                    </div>
                    <div class="card-body">
                        @csrf
                        {{-- Nombre --}}
                        <div class="form-group">
                            <label for="nombre">Nombre (*)</label>
                            <input type="text" id="nombre" name="nombre" 
                                value="{{ old("nombre")? old("nombre"): $producto->nombre }}" 
                                class="form-control form-control-sm @error("nombre") is-invalid @enderror">
                            @error("nombre")
                                <div class='invalid-feedback'>{{ $message }}</div> 
                            @enderror
                        </div>
                        {{-- Nombre --}}
                        <div class="form-group">
                            <label for="precio">Precio (*)</label>
                            <input type="text" id="precio" name="precio" 
                                value="{{ old("precio")? old("precio"): $producto->precio }}" 
                                class="form-control form-control-sm @error("precio") is-invalid @enderror">
                            @error("precio")
                                <div class='invalid-feedback'>{{ $message }}</div> 
                            @enderror
                        </div>
                        {{-- Descripcion --}}
                        <div class="form-group">
                            <label for="descripcion">Descripcion</label>
                            <textarea id="descripcion" name="descripcion" rows="3"
                                class="form-control form-control-sm @error("descripcion") is-invalid @enderror"
                            >{{ old("descripcion")? old("descripcion"): $producto->descripcion}}</textarea>
                            @error("descripcion")
                                <div class='invalid-feedback'>{{ $message }}</div> 
                            @enderror
                        </div>
                    </div>
                    <div class="card-footer text-right">
                        <a href="{{ url("/productos") }}" class="btn btn-secondary">Cancelar</a>
                        @if$action=='create' ) <button type="submit" class="btn btn-primary">Guardar</button> @endif
                        @if$action=='edit' ) <button type="submit" class="btn btn-success">Editar</button> @endif
                    </div>
                </div>
                
            @if$action!='show' ) </form> @endif
            
        </div>
    </div>
@endsection

6. Rutas

Para finalizar no olvides establecer las rutas para nuestro crud. Para ello editamos el siguiente archivo:
routes\web.php
<?php
use Illuminate\Support\Facades\Route;
Route::resource('/productos''ProductoController');
 Nota que estoy utilizando el método Route::resource esto genera todas las rutas necesarias para implementar un crud completo. Si ejecutas el comando:
php artisan route:list
Puedes visualizar las rutas generadas para productos:


Bueno espero te haya servido este artículo, si tienes alguna duda puedes escribirme o dejarlo en los comentarios. ¡Comparte la información gracias!

Comentarios