Optimización de consultas SQL

En esta lección prepararemos un sencillo listado de productos con categoría utilizando Laravel y los diferentes componentes que provee el ORM Eloquent (modelos, migraciones, model factories y seeders).

Crear un nuevo proyecto con Laravel 6

Vamos a crear un nuevo proyecto de Laravel. Yo utilizaré la versión 6, pero los conocimientos de este tip están disponibles desde la versión 5.3 en adelante:

#
composer create-project laravel/laravel fast_eloquent "6.*"

Generar modelos, factories y seeders con Laravel

Ahora vamos al directorio del proyecto y generemos 2 modelos con sus migraciones, model factories y seeders:

#
cd fast_eloquent
php artisan make:model ProductCategory -mfs
php artisan make:model Product -mfs

Modificando las migraciones

En la migración de ProductCategory vamos a agregar algunas columnas, las esenciales son title y slug, pero supongamos que requerimos de otras 5 columnas:

<?php
//...

class CreateProductCategoriesTable extends Migration
{
    public function up()
    {
        Schema::create('product_categories', function (Blueprint $table) {
            $table->bigIncrements('id');

            $table->string('title');
            $table->string('slug');
            $table->mediumText('description');
            
            // Agrega al menos 4 columnas extra

            $table->timestamps();
        });
    }

    //...
}

La migración de la tabla de productos será similar, aunque vamos a incluir un campo para imágenes y también debemos incluir un campo para el ID de la categoría:

<?php

//...

class CreateProductsTable extends Migration
{
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->bigIncrements('id');

            $table->unsignedBigInteger('category_id');

            $table->string('title');
            $table->string('slug');
            $table->string('image');
            $table->mediumText('description');

            // Agrega al menos 3 columnas adicionales

            $table->timestamps();
        });
    }

    //...
}

Modificando los Model Factories

Usemos Faker para generar datos aleatorios para ambos modelos:

Primero en database/factories/ProductCategoryFactory.php:

<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\ProductCategory;
use Faker\Generator as Faker;

$factory->define(ProductCategory::class, function (Faker $faker) {
    return [
        'title' => $faker->sentence(2),
        'slug' => $faker->slug,
        'description' => $faker->paragraph,
        // Continua con el resto de las columnas
    ];
});

Luego en database/factories/ProductFactory.php, el proceso es similar en ambos factories:

<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Product;
use Faker\Generator as Faker;

$factory->define(Product::class, function (Faker $faker) {
    return [
        'category_id' => factory(\App\ProductCategory::class),
        'title' => $faker->sentence(4),
        'slug' => $faker->slug,
        'image' => $faker->randomElement(['img1.jpg', 'img2.jpg', 'img3.jpg', 'img4.jpg']),
        'description' => $faker->paragraph,
    ];
});

Modificando los Seeders

A continuación, modifiquemos los seeders para generar 50 categorías y alrededor de 1000 productos asociados a dichas categorías:

<?php

use Illuminate\Database\Seeder;

class ProductCategorySeeder extends Seeder
{
    public function run()
    {
        factory(\App\ProductCategory::class)->times(200)->create();
    }
}
<?php

use Illuminate\Database\Seeder;

class ProductSeeder extends Seeder
{
    public function run()
    {
        $categories = \App\ProductCategory::pluck('id');

        $categories->each(function ($category) {
            factory(\App\Product::class)->times(rand(12, 28))->create([
                'category_id' => $category,
            ]);
        });
    }
}

No olvides agregar el llamado a los seeders dentro de DatabaseSeeder:

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
         $this->call([ProductCategorySeeder::class, ProductSeeder::class]);
    }
}

Debes cargar los datos en el orden correcto, por ejemplo, los productos dependen de la categoría del producto, por lo tanto, cargamos las categorías de producto primero y luego los productos.

Crear tablas con datos en Laravel

Para ejecutar las migraciones y los seeders primero debemos configurar la base de datos, como vimos en la lección Creación de tablas con el sistema de migraciones en Laravel 6, simplemente edita el archivo .env y modifica las siguientes llaves con los valores que correspondan a tu entorno:

###
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=fast_eloquent
DB_USERNAME=root
DB_PASSWORD=root

Relacionar productos y categorías con Eloquent

Antes de crear el listado, vamos a indicar que un producto pertenece a una categoría, dentro del modelo de productos en app/Product.php:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    public function category()
    {
        return $this->belongsTo(ProductCategory::class);
    }
}

Además, indicaremos que cada categoría puede tener muchos productos en app/ProductCategory.php:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class ProductCategory extends Model
{
    public function product()
    {
        return $this->hasMany(Product::class);
    }
}

Generar un listado de productos con Laravel

A continuación, vamos a generar un listado de productos utilizando una ruta y una vista. Primero declaremos la ruta en routes/web.php:

<?php

Route::get('/productos', function () {
    $products = \App\Product::all();
    
    return view('products', ['products' => $products]);
});

Luego creemos una plantilla de Blade en resources/views/products.blade.php con el siguiente código:

<!doctype html>
<html lang="es">
<head>
    <meta charset="UTF-8">
</head>
<body>
    <ul>
        @foreach($products as $product)
            <li>
                <a href="{{ url('productos/'.$product->slug) }}">
                    <strong>{{ $product->title }}</strong>
                </a> -
                <a href="{{ url('productos/categoria/'.$product->category->slug) }}">
                    {{ $product->category->title }}
                </a>
            </li>
        @endforeach
    </ul>
</body>
</html>

Agregando estilos CSS a una aplicación de Laravel

Si quieres ver el listado con estilos de CSS e imágenes puedes descargar los siguientes archivos:

Mira el código en GitHub: actual, resultado, comparación.

La plantilla de productos es muy similar a la presentada arriba (salvo por los cambios al HTML), el único cambio con Blade es la inclusión de la imagen:

<!-- resources/views/products.blade.php -->
<img src="{{ asset("img/products/{$product->image}") }}" alt="Product image" width="300px" height="200px" class="item-image">

Nota que en la base de datos guardamos solo el nombre de la imagen y en el sistema de archivos guardamos la imagen como tal (en un proyecto real típicamente irían en el directorio storage/app/public, yo las he colocado en public/img/products).

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.

Lección siguiente Tip de optimización con Eloquent #1: Verifica las consultas ejecutadas