Link Search Menu Expand Document

5.11. Usando la BD con Eloquent

Eloquent uno de los componentes de Laravel que permiten al desarrollador manipular los datos de la BD sin rebajarse a escribir sucio SQL. Y lo consigue mediante un mecanismo simple y elegante: el mapeo objeto-relacional.

En esta sección vamos a ver qué es eso del mapeo y cómo sacarle a Eloquent el máximo partido para que nuestros modelos se programen prácticamente solos.

5.11.1. ¿Qué es el mapeo objeto-relacional?

Eloquent no es más que una librería incluida con Laravel que utiliza un patrón de software llamado ORM (Object-Relational Mapping) para abstraer aún más el acceso a la base de datos, de manera que no tengamos que escribir y depurar SQL.

Mapear los objetos de nuestra aplicación con una BD relacional significa que Eloquent convierte los registros de tus tablas en objetos de tu aplicación para que los manipules con mayor facilidad.

Sí, lo has entendido bien: podrás manejar los datos de tu base de datos como si fueran objetos de tu aplicación. Y, cuando los modifiques, borras o crees, se ejecutará el código SQL necesario (sin que tú te enteres) para traducir esas operaciones en sentencias para la base de datos.

Te lo muestro con un ejemplo.

Imagina que tenemos una tabla de artículos de un blog. La llamaremos Articles, y tendrá 3 campos: el id (entero), el title (cadena de caracteres) y el body (cadena de caracteres). Con Eloquent, usar esa tabla es tan fácil como hacer algo así:

$art = Article::find('7');        // Buscamos un artículo por su id
echo $art->title;                 // Ahora podemos acceder a los campos de ese artículo
$art->body = "Texto del cuerpo";  // O también podemos modificar los campos del artículo
$art->save();                     // Si hacemos save(), los cambios se guardan en la BD

5.11.2. ¿Cómo puedo usar Eloquent en mi aplicación?

Tienes que crear un modelo. ¿Qué te creías? Pero con Artisan es así de fácil:

$ php artisan make:model <Mi-modelo>

Por ejemplo:

$ php artisan make:model Article

El modelo se creará en /app/models/Article.php (¡cuidado! En versiones más antiguas de Laravel, el modelo se creará en /app/Article.php)

Truco: si creas el modelo con la opción -m, se creará atomáticamente su migración, lo cual resulta tremendamente práctico:

$ php artisan make:model Article -m

Ya tienes tu modelo. Si no puedes contener tu curiosidad insaciable y lo abres, verás un archivo bastante decepcionante con este aspecto:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use HasFactory;
}

¿Y eso es todo?

Pues sí, eso es todo. La gracia está en ese “extends Model” de la definición de la clase: aunque tu modelo Article parezca vacío, en realidad acaba de heredar un mogollón de características útiles de la clase Model. Entre otras, todos los métodos de Eloquent.

5.11.3. Pero ¿cómo sabe Laravel qué tabla está asociada a ese modelo?

Buena pregunta.

Laravel supondrá que la tabla se llama igual que el modelo, solo que en minúscula y plural. Es decir, la tabla de la base de datos asociada al modelo Article se debería llamar articles.

Recuerda que en Laravel existen un montón de convenciones sobre los nombres de las cosas que conviene respetar, más que nada porque te facilitan mucho la vida y hacen que te centres en lo verdaderamente importante (crear el código de tu aplicación) en lugar de en lo accesorio (pelearte con los nombres).

No obstante, si quieres ponerle otro nombre a la tabla, puedes hacerlo. También puedes cambiar otras muchas cosas, como el nombre del campo clave (Laravel supondrá que se llama id) o los campos sobre los que se puede hacer una asignación masiva, algo muy útil que veremos un poco más adelante.

Por ejemplo, podemos editar el modelo /app/Articles.php y añadir estas líneas dentro de nuestra clase:

protected $table = 'articulos';   // El nombre de la tabla no será "articles" sino "articulos"
protected $primaryKey = 'id_art'; // La clave primaria no será "id" sino "id_art"
protected $fillable = array('id','titulo','cuerpo'); // Campos de la tabla en los que se permite la ASIGNACIÓN MASIVA (más adelante veremos qué es esto) 

5.11.4. Consultas con Eloquent

Solo con construir el modelo (y asignarlo a la tabla adecuada) ya tenemos detrás a Eloquent haciendo el mapeo objeto-relacional.

Ahora podemos ir a nuestro controlador (que, por respetar las convenciones de Laravel, debería llamarse ArticlesController) y lanzar consultas con expresiones como estas:

$articlesList = Article::all();        // Devuelve todos los artículos
$myArticle = Article::find($id);       // Devuelve el artículo con ese $id
$myArticle = Article::findOrFail($id); // Error 404 si el artículo no existe
$articlesList = Article::where('id', '>', 100)->get();  // Select con where
$articlesList = Article::where('id', '>', 100)->take(10)->get();  // Select con where y take
$maxId = Article::max('id');           // Devuelve el último id asignado

A lo mejor hay algo que te está chirriando. Si escribimos cosas como estas en el controlador, ¿no significa eso que estamos lanzando consultas desde el controlador? Y eso está prohibidísimo en la arquitectura MVC. ¿Es que Laravel no respeta la arquitectura MVC?

Hay quien dice que sí, hay quien dice que no y hay quien dice que psé, psé.

En realidad, no estamos escribiendo SQL en el controlador, ni estamos accediendo a la base de datos de ninguna manera explícita. Cuando lanzamos un Article::find($id) desde el controlador, por ejemplo, no sabemos qué ocurre por debajo ni de dónde se extrae la información: nos limitamos a acceder a una de nuestras clases y obtener de ella un objeto de tipo Article.

5.11.5. Inserciones y borrados con Eloquent

También podemos usar Eloquent para insertar un nuevo artículo desde nuestro controlador:

   $art = new Article;
   $art->title = 'Los Chitauri invaden Nueva York';
   $art->body = 'Bla, bla, bla';
   $art->save();

Si los datos del artículo vienen de un formulario, fíjate en lo alucinantemente fácil que es recoger todos esos datos, crear un objeto Article con ellos y guardar el artículo en la BD:

   public function store(Request $request) {
      Article::create($request->all());  // Esto es una ASIGNACIÓN MASIVA de las que hablábamos más arriba!!
      return view('la-vista-que-sea');
   }

Ojo: solo los campos que hayas indicado como fillables en el modelo se podrán asignar al artículo de este modo. Eso es lo que significa asignación masiva.

Y, por supuesto, también podemos modificar y borrar artículos de la base de datos:

   $art = Article::find(18);     // MODIFICAR
   $art->body = 'Nuevo cuerpo';
   $art->save();
   $art = Article::find(13);     // BORRAR
   $art->delete();

5.11.6. Lista de los métodos más útiles de Eloquent

Hemos visto en los últimos ejemplos algunos métodos de Eloquent por separado. Te los reúno en esta sección para que los puedas consultar cuando lo necesites.

Aviso: no están todos, solo los de uso más habitual. Si quieres una lista completa, ya sabes: acude a la documentación oficial.

  • all() → Recupera todos los registros de una tabla.
  • where(“campo”, valor) → Aplica claúsula where.
  • orderBy(“campo”, “asc|desc”) → Aplica claúsula order by.
  • get() → Recupera registros seleccionados. Se suele usar con where y/o order by:
    Ciudades::where("ciudad", "Madrid")->orderBy("id", "asc")->get();
    
  • first() → Recupera el primer registro.
  • latest() → Recupera el último registro.
  • find(valor) → Busca registros con ese valor en el campo id.
  • findOrFail(valor) → Lanza un error 404 si no encuentra el registro.
  • count(), max(), min()… → Utiliza funciones de agregado de SQL.
  • save() → Inserta o actualiza registros.
  • update() → Actualiza registros.
  • delete() → Elimina registros.

5.11.7. Relaciones entre tablas con Eloquent

Las relaciones entre tablas también se pueden manejar con Eloquent sin necesidad de andar escribiendo larguísimos INNER JOIN y otros miembros de su nutrida familia, con todos los errores de escritura que suelen hacernos perder el tiempo depurando SQL.

Aunque al principio te parezca que definir las relaciones entre tablas con Eloquent necesita mucho trabajo previo, te garantizo que después te alegrarás de haberlo hecho. Porque las relaciones, una vez definidas, se comportan como consultas y se puede operar con ellas como si lo fueran.

Lo comprenderemos mejor, como siempre, con ejemplos. En los siguientes apartados, vamos a suponer que tenemos estas tablas:

  • usuarios(id#, nombre, passwd)
  • emails(id#, email, usuario_id) → Esta tabla tiene una relación 1:1 con usuarios
  • articulos(id#, titulo, texto, idUsuario) → Esta tabla tiene una relación 1:N con usuarios
  • roles(id#, nombre) → Esta tabla tiene una relación N:N con usuarios

ATENCIÓN: en la tabla artículos hemos usado a propósito un nombre no estándar para la clave foránea. Para respetar la convención de Laravel, debería llamarse usuario_id en lugar de idUsuario, como en la tabla emails.

5.11.7.1. Relaciones 1:1 (usuarios <-> emails)

Para definir un relación 1:1 con Eloquent debes hacer lo siguiente:

Paso 1. En el modelo de la tabla maestra (clase Usuario, en nuestro ejemplo) añadimos este método:

public function email() {
    return $this->hasOne('App\Email'); 
}

Paso 2. En el modelo de la tabla relacionada (clase Email) añadimos este método:

    public function usuario() {
        return $this->belongsTo('App\Usuario'); 
    }

A partir de ahora, se puede recuperar el email de un usuario como si fuera un miembro de la clase Usuario, tan sencilla como esto:

    $email = Usuario::find(1)->email;
    $user = Email::all()->first()->user;

Y también funciona al revés. Es decir, a partir de un objeto de tipo Email, podemos acceder a su usuario como si fuera un atributo de la clase Email.

5.11.7.2. Relaciones 1:N (usuarios <-> artículos)

Si tienes una relación 1:N (como la que hay entre las tablas de usuarios y artículos de nuestro ejemplo), para definirla en Eloquent tienes que hacer esto:

Paso 1. En el modelo de la tabla maestra (clase Usuario), añade este método:

public function articulos() {
    return $this->hasMany('App\Articulo', 'idUsuario'); 
}
// ATENCIÓN: hemos tenido que indicar el nombre de la clave foránea
// (idUsuario) porque no habíamos respetado la convención de Laravel
// (usuario_id) al crear la tabla de artículos

Paso 2. En el modelo de la tabla relacionada (class Articulo), añade este otro método:

    public function usuario() {
        return $this->belongsTo('App\Usuario'); 
    }

¡Y listo! Ya puedes recuperar los artículos a partir del usuario, como si fueran atributos de esa clase. Y a la inversa también funciona.

Por ejemplo:

    $articulos = Usuario::find(1)->articulos;
    foreach ($articulos as $articulo) {
       // Procesar cada artículo
    }

5.7.11.3. Relaciones N:N (usuarios <-> roles)

Si lo que tienes es una relación N:N (como la que hay entre usuarios y roles en nuestro ejemplo), los pasos a seguir para construirla con Eloquent son estos:

Paso 1. En el modelo de una de las tablas (clase Usuario) añadimos este método:

public function roles() {
    return $this->belongsToMany('App\Rol'); 
}

Paso 2. En el modelo de la otra tabla (clase Rol) añadimos este método:

    public function usuarios() {
        return $this->belongsToMany('App\Usuario'); 
    }

Ahora, ya se pueden recuperar los roles a partir del usuario o a la inversa. Por ejemplo:

    $roles = Usuario::find(1)->roles;
    foreach ($roles as $rol) {
       // Procesar cada rol
    }

5.7.11.4. La tabla pivote: insertar, modificar y borrar en relaciones N:N

Insertar, modificar y borrar en relaciones N:N implica escribir datos (normalmente, ids) en la tabla intermedia o tabla pivote, lo cual suele suponer un engorro cuando se hace a mano con SQL.

Ese tedioso proceso también se puede automatizar con Eloquent. Lo vemos con un ejemplo entre nuestras tablas usuarios y roles.

Para insertar un usuario y sus roles se usa el método attach():

public function store(Request $r) {
    $user = new User($r->all());
    $user->roles()->attach($r->roles);
    $user->save();
}

Para actualizar un usuario y sus roles se usa el método sync():

    public function update(Request $r, $id) {
        $user = User::find($id);
        $user->fill($r->all()); 
        $user->roles()->sync($r->roles); 
        $user->save();  
    }

Para eliminar un usuario y sus roles se usa el método detach():

    public function destroy($id) {
        $user = User::find($id);
        $user->roles()->detach(); 
        $user->delete(); 
    }

5.7.11.5. Problemas frecuentes en relaciones N:N

Eloquent supondrá que el nombre de la tabla de la relación se ha formado con los nombres de las dos tablas maestras en snake case y ordenadas alfabéticamente.

Por ejemplo, en la relación N:N entre usuarios y roles, Eloquent supondrá que existe una tabla llamada roles_usuarios. Si no se llama así, la relación fallará.

Se puede indicar otro nombre de tabla al definir la relación. Por ejemplo, en el modelo de usuarios (clase Usuario):

public function roles() {
    return $this->belongsToMany('App\Rol', 'usuarios_roles'); 
}

También se pueden indicar los nombres de las claves foráneas si no siguen las convenciones (que, según Laravel, son usuario_id, rol_id, etc)

    public function roles() {
        return $this->belongsToMany('App\Rol', 'usuarios_roles',
                                    'id_usuario', 'id_rol'); 
    }

¿Te has fijado en que hemos creado un método para acceder a la tabla relacionada, pero estamos usando un atributo en su lugar?

public function articulos() {
    return $this->hasMany('App\Articulo'); 
}
public function loQueSea() {
    $arts = Usuario::find(1)->articulos;  // articulos, no articulos()
}

Pues bien, el atributo articulo es un atributo virtual creado por Eloquent. Pero el método articulos() también existe, y puede usarse como una consulta, extendiéndola como necesitemos. Por ejemplo:

$arts = Usuario::find(1)->articulos()->where('titulo','foo')->first();