Eloquent en Laravel: models, relaciones, CRUD y consultas
Eloquent es el ORM de Laravel: permite representar registros de una base de datos mediante objetos PHP y expresar consultas, relaciones y operaciones CRUD a través de models. Facilita enormemente el acceso a datos, pero no elimina SQL. Para utilizar Eloquent correctamente necesitas comprender cómo están relacionadas tus tablas, qué consultas genera el ORM y cuándo una abstracción cómoda puede producir problemas de rendimiento.
¿Qué es Eloquent en Laravel?
Eloquent es un ORM que permite trabajar con registros de una base de datos mediante clases y objetos PHP.
Normalmente una clase llamada Producto puede representar registros de una tabla de productos, mientras métodos y relaciones permiten consultar, insertar, actualizar y conectar datos.
Por ejemplo, en vez de escribir manualmente una consulta SELECT sencilla, puedes expresar:
$productos = Producto::query()
->where('activo', true)
->orderBy('nombre')
->get();
Eloquent hace el código más expresivo, pero la consulta SQL continúa existiendo debajo de esa abstracción.
Los conceptos que necesitas dominar
Models
Representan entidades persistidas.
Queries
Filtrar, ordenar y recuperar datos.
Relaciones
Conectan modelos entre sí.
Eager loading
Reduce consultas innecesarias.
Scopes
Reutilizan condiciones frecuentes.
Rendimiento
SQL, índices y consultas.
¿Qué problema intenta resolver un ORM?
Una aplicación orientada a objetos trabaja con clases, objetos, propiedades y métodos.
Una base de datos relacional trabaja con tablas, filas, columnas, claves y relaciones.
Son dos formas diferentes de representar información.
Un ORM intenta ofrecer una capa que permita trabajar con datos relacionales utilizando objetos del lenguaje.
Tabla productos
↓
Model Producto
↓
Objeto Producto
↓
PHP
Esto puede reducir mucho código repetitivo, pero no elimina el modelo relacional que existe debajo.
Necesitas comprender tablas, claves, relaciones, índices y consultas para utilizar el ORM con criterio.
Un model Eloquent representa una entidad persistida
Supongamos que tenemos una tabla llamada:
productos
Podemos representar esos datos mediante un model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
final class Producto extends Model
{
protected $fillable = [
'nombre',
'precio',
'activo'
];
}
A partir de ahí, la clase puede utilizarse para realizar operaciones sobre esos registros.
El model no debería entenderse simplemente como “una tabla convertida en clase”
Puede representar relaciones, reglas relacionadas con persistencia, transformaciones y comportamiento asociado a esa entidad.
Pero tampoco deberías convertir cada model en una clase gigantesca que contenga toda la lógica del sistema.
Eloquent utiliza un query builder para construir consultas de forma expresiva
Obtener todos
$productos = Producto::all();
Buscar por ID
$producto = Producto::find($id);
Filtrar
$productos = Producto::query()
->where('activo', true)
->get();
Ordenar y limitar
$productos = Producto::query()
->where('activo', true)
->orderByDesc('created_at')
->limit(10)
->get();
Es fácil leer este código como PHP.
Pero también necesitas imaginar qué consulta SQL puede existir debajo.
Crear registros con Eloquent
Podemos insertar un producto utilizando el model.
$producto = Producto::create([
'nombre' => 'Monitor 27"',
'precio' => 199990,
'activo' => true
]);
El resultado es un objeto que representa el registro creado.
También puedes instanciar el model
$producto = new Producto();
$producto->nombre = 'Teclado mecánico';
$producto->precio = 69990;
$producto->activo = true;
$producto->save();
Ambos enfoques pueden ser válidos.
Lo importante es comprender qué datos estás permitiendo persistir.
Mass assignment merece atención cuando utilizas create() o fill()
Una práctica peligrosa sería asumir que todos los datos enviados por el usuario pueden persistirse sin control.
Producto::create(
$request->all()
);
El problema es que el cliente podría enviar campos que no esperabas.
Una alternativa mucho más clara es validar primero:
$datos = $request->validate([
'nombre' => [
'required',
'string',
'max:150'
],
'precio' => [
'required',
'numeric',
'min:0'
]
]);
Producto::create($datos);
Que un atributo pueda asignarse no significa que cualquier usuario tenga permiso para modificarlo.
Actualizar y eliminar registros
Actualizar un model
$producto = Producto::findOrFail($id);
$producto->update([
'nombre' => 'Monitor 32"',
'precio' => 249990
]);
Eliminar
$producto = Producto::findOrFail($id);
$producto->delete();
Encontrar no significa autorizar
Poder recuperar un registro mediante su ID no demuestra que el usuario actual tenga permiso para modificarlo o eliminarlo.
Esa comprobación pertenece a la autorización del sistema.
Las relaciones son una de las partes más importantes de Eloquent
Las aplicaciones reales no suelen trabajar con tablas aisladas.
Un usuario puede tener pedidos.
Un pedido puede tener productos.
Un artículo puede pertenecer a una categoría.
Eloquent permite expresar estas asociaciones mediante métodos en los models.
Usuario
│
└── tiene muchos → Pedidos
Pedido
│
└── pertenece a → Usuario
Para utilizar correctamente estas relaciones, primero deberías entender primary keys, foreign keys y cardinalidad en una base de datos relacional.
Relación uno a muchos: un usuario puede tener muchos pedidos
Dentro del model Usuario:
<?php
public function pedidos()
{
return $this->hasMany(
Pedido::class
);
}
Esto expresa que un usuario puede estar relacionado con múltiples pedidos.
Después puedes acceder a la relación:
$usuario = Usuario::findOrFail($id);
$pedidos = $usuario->pedidos;
Parece simplemente acceso a una propiedad, pero puede implicar una consulta a la base de datos.
La relación inversa permite saber a qué usuario pertenece un pedido
<?php
public function usuario()
{
return $this->belongsTo(
Usuario::class
);
}
Ahora podemos:
$pedido = Pedido::findOrFail($id);
$usuario = $pedido->usuario;
La base de datos normalmente mantiene la referencia mediante una foreign key.
Un pedido puede contener muchos productos y un producto aparecer en muchos pedidos
Este tipo de relación normalmente necesita una tabla intermedia.
pedidos
↓
pedido_producto
↑
productos
Dentro del model Pedido:
<?php
public function productos()
{
return $this->belongsToMany(
Producto::class
);
}
La tabla intermedia también puede almacenar datos propios de la relación.
Por ejemplo:
Cantidad.
Precio aplicado.
Descuento.
Esto demuestra que una tabla pivote no es solamente una formalidad técnica: puede representar información propia de la relación.
Acceder a una relación puede ejecutar una consulta adicional
Considera:
$pedidos = Pedido::all();
foreach ($pedidos as $pedido) {
echo $pedido->usuario->nombre;
}
Visualmente, el código parece simple.
Pero si cada acceso a:
$pedido->usuario
provoca una consulta, puedes generar muchísimas operaciones innecesarias.
Aquí aparece uno de los conceptos más importantes al trabajar con ORM.
El problema N+1 puede convertir código elegante en una aplicación lenta
Imagina que cargas 100 pedidos.
Primera consulta:
SELECT * FROM pedidos;
Después, si cada pedido carga su usuario individualmente, podrías terminar con muchas consultas adicionales.
1 consulta para pedidos
+
100 consultas para usuarios
=
101 consultas
with() permite cargar relaciones que sabes que vas a necesitar
$pedidos = Pedido::query()
->with('usuario')
->get();
En lugar de esperar hasta cada acceso, indicas de antemano que necesitas la relación.
También puedes cargar varias relaciones
$pedidos = Pedido::query()
->with([
'usuario',
'productos'
])
->get();
Esto no significa que debas cargar absolutamente todas las relaciones siempre.
Cada relación incorpora datos y trabajo.
Recupera lo que realmente necesita cada caso de uso.
No siempre necesitas SELECT *
Si una pantalla solamente necesita:
ID, nombre y precio, puedes expresar esa necesidad.
$productos = Producto::query()
->select([
'id',
'nombre',
'precio'
])
->where('activo', true)
->get();
Esto puede ser especialmente importante cuando las tablas contienen columnas grandes que esa operación no necesita.
La optimización empieza por entender qué necesita realmente la consulta
No por añadir técnicas complejas automáticamente.
Los scopes pueden expresar condiciones que repites constantemente
Supongamos que casi siempre necesitas productos activos.
Sin reutilización:
Producto::query()
->where('activo', true)
->get();
Un scope puede encapsular esa intención.
El objetivo no es esconder todas las consultas, sino dar un nombre claro a condiciones que representan conceptos del dominio.
No necesitas cargar todos los registros para contarlos
Una mala estrategia sería recuperar miles de objetos solamente para conocer cuántos existen.
Puedes preguntar directamente por el conteo.
$total = Producto::query()
->where('activo', true)
->count();
Lo mismo ocurre con operaciones de suma, promedio, mínimo o máximo.
Deja que la base de datos realice el trabajo que sabe hacer eficientemente.
Una lista grande no debería cargar todos sus registros en una sola página
Si tienes 100.000 productos, esto:
$productos = Producto::all();
probablemente no representa lo que necesita una interfaz paginada.
Puedes solicitar una cantidad limitada por página.
$productos = Producto::query()
->where('activo', true)
->orderBy('id')
->paginate(20);
Para conjuntos extremadamente grandes y determinados patrones de navegación, existen estrategias alternativas a offsets profundos.
Pero para empezar, lo importante es comprender que paginar también forma parte del diseño de la consulta.
Eloquent no elimina la necesidad de transacciones
Imagina la creación de un pedido.
Necesitas:
Crear pedido.
Crear sus líneas.
Actualizar inventario.
Si la operación falla a mitad, puedes terminar con datos inconsistentes.
Una transacción permite tratar varias operaciones como una unidad.
<?php
use Illuminate\Support\Facades\DB;
DB::transaction(function () use ($datos) {
$pedido = Pedido::create([
'usuario_id' => $datos['usuario_id'],
'total' => $datos['total']
]);
foreach ($datos['items'] as $item) {
$pedido->items()->create($item);
}
});
Las transacciones siguen siendo una herramienta fundamental del modelo relacional.
No todas las consultas necesitan convertirse en models completos
Eloquent resulta especialmente útil cuando trabajas con entidades y relaciones.
Pero existen consultas orientadas a reportes, métricas o agregaciones donde un enfoque más directo puede resultar más claro.
Laravel también proporciona Query Builder.
<?php
use Illuminate\Support\Facades\DB;
$ventas = DB::table('pedidos')
->selectRaw(
'DATE(created_at) AS fecha, SUM(total) AS total'
)
->groupByRaw(
'DATE(created_at)'
)
->get();
Elegir Eloquent no obliga a utilizarlo para absolutamente todo
Utiliza la herramienta que exprese mejor la consulta y su propósito.
En algunos casos una consulta SQL explícita puede ser más clara
Un ORM es una herramienta.
No una regla que prohíba escribir SQL.
Consultas particularmente complejas, reportes, agregaciones o necesidades específicas pueden justificar otras alternativas.
La decisión debería basarse en claridad y comportamiento
No en una idea de que:
“SQL es malo” o “ORM es siempre mejor”.
Cuanto mejor sepas SQL, mejores decisiones podrás tomar dentro de Eloquent
JOIN, índices, transacciones, consultas y rendimiento.
Eloquent no puede compensar una base de datos mal indexada
Imagina una consulta que filtra constantemente millones de registros por una columna.
El problema puede estar mucho más relacionado con el diseño de índices que con Eloquent.
Un ORM puede construir la consulta, pero MySQL sigue siendo responsable de ejecutarla.
No indexes todo automáticamente
Los índices aceleran determinados patrones de lectura, pero también ocupan espacio y añaden trabajo en escrituras.
Diseña índices según las consultas reales de la aplicación.
No conviertas tus models Eloquent en el lugar donde vive toda la aplicación
Un model puede representar datos, relaciones y comportamiento relacionado con esa entidad.
Pero un sistema complejo puede incluir procesos que coordinan múltiples entidades.
Por ejemplo:
Crear pedido.
Reservar stock.
Aplicar descuento.
Registrar pago.
Enviar notificación.
Meter todo dentro de Pedido.php puede terminar creando un model difícil de mantener.
Servicios, actions o casos de uso pueden ayudar cuando existe una responsabilidad real que separar.
Repasar POO en PHP →No todos los registros deberían desaparecer físicamente al “eliminarlos”
En determinados sistemas necesitas conservar registros por trazabilidad, historial o recuperación.
Laravel permite trabajar con mecanismos de eliminación lógica cuando el modelo del negocio lo requiere.
Pero no conviertas soft delete en una regla universal
Existen datos que realmente deberían eliminarse y otros que deben conservarse.
Esa decisión pertenece al modelo de negocio y a los requisitos de datos, no únicamente al ORM.
Evita asumir que todo atributo del model debe exponerse en una API
Un model puede contener información que la base de datos necesita, pero que el cliente nunca debería recibir.
Por ejemplo:
Campos internos.
Información privada.
Metadatos administrativos.
Relaciones no necesarias.
La respuesta de una API debería representar el contrato que necesita el consumidor, no simplemente volcar el contenido completo del model.
Eloquent resuelve persistencia; HTTP y el contrato de la API siguen siendo otra responsabilidad
JSON, recursos, status codes y seguridad.
Las consultas y relaciones importantes también deberían probarse
No basta con comprobar que una página “se ve bien”.
Puedes probar comportamientos como:
Crear un registro.
Asociar un pedido a un usuario.
Evitar datos inválidos.
Filtrar correctamente registros.
Mantener consistencia durante transacciones.
El objetivo no es probar que Laravel funciona.
Es comprobar que tus reglas y consultas producen el comportamiento que esperas.
PDO te acerca a SQL; Eloquent te acerca al modelo de objetos
| Área | PDO | Eloquent |
|---|---|---|
| SQL visible | Muy directo | Más abstraído |
| Models | Debes diseñarlos | Integrados |
| Relaciones | SQL / lógica propia | API expresiva |
| CRUD repetitivo | Más manual | Muy rápido |
| Control consulta | Muy explícito | Debes observar SQL generado |
| Laravel | Posible a nivel de base PHP | Integración natural |
No necesitas escoger uno como si el otro estuviera prohibido.
Aprender PDO primero puede ayudarte a entender qué está abstrayendo Eloquent.
Eloquent es un buen ejemplo de lo que Laravel añade sobre PHP puro
Models, relaciones y consultas expresivas.
En qué orden aprender Eloquent
SQL
SELECT, JOIN y relaciones.
Models
Entiende qué representa cada clase.
CRUD
Crear, leer, actualizar y eliminar.
Relaciones
belongsTo, hasMany y many-to-many.
Eager loading
Evita N+1.
Scopes
Reutiliza consultas con intención.
Paginación
No cargues todo de una vez.
Transacciones
Protege operaciones múltiples.
Rendimiento
SQL, índices y consultas.
Arquitectura
Decide cuándo no usar Eloquent.
Los errores que más deberías evitar con Eloquent
Eloquent es potente cuando entiendes tanto los objetos PHP como la base de datos que existe debajo
Utiliza models para representar entidades persistidas.
Aprende CRUD antes de entrar a relaciones complejas.
Comprende hasMany, belongsTo y relaciones muchos a muchos.
Detecta el problema N+1 y utiliza eager loading cuando corresponda.
No recuperes columnas o registros que no necesitas.
Utiliza paginación en listados grandes.
Usa transacciones cuando varias escrituras forman una sola operación.
Aprende Query Builder y SQL para no depender de una sola abstracción.
Eloquent debería hacer tus consultas más expresivas, no impedirte comprender qué está haciendo realmente la base de datos.
Continúa desde Eloquent hacia Laravel completo
Preguntas sobre Eloquent en Laravel
Eloquent es el ORM de Laravel. Permite representar registros de una base de datos mediante models PHP y realizar consultas, CRUD y relaciones utilizando una API orientada a objetos.
Sí, es muy recomendable. Eloquent abstrae muchas consultas, pero SQL, relaciones, JOIN, índices y transacciones siguen siendo fundamentales para diseñar y optimizar una aplicación.
Eloquent trabaja alrededor de models y relaciones orientadas a objetos. Query Builder permite construir consultas sin necesitar representar cada resultado mediante un model Eloquent. Ambos pueden ser útiles según el tipo de consulta.
hasMany representa una relación uno a muchos. Por ejemplo, un usuario puede tener muchos pedidos. El model Usuario puede definir una relación hasMany hacia Pedido.
belongsTo expresa que un model pertenece a otro. Por ejemplo, un Pedido puede pertenecer a un Usuario. Normalmente la relación se apoya en una foreign key de la base de datos.
N+1 aparece cuando recuperas una colección y después ejecutas consultas adicionales para sus relaciones registro por registro. El eager loading permite cargar relaciones necesarias de forma más eficiente.
Eloquent ofrece una abstracción de acceso a datos mucho más orientada a models y relaciones. PDO es una interfaz de PHP de menor nivel para trabajar con bases de datos. Aprender PDO y SQL puede ayudarte a comprender mejor lo que el ORM abstrae.
No necesariamente. Eloquent resulta muy cómodo para trabajar con models y relaciones, pero determinadas consultas, reportes, agregaciones o necesidades específicas pueden expresarse mejor mediante Query Builder o SQL más directo.
Aprende a leer Eloquent como PHP y, al mismo tiempo, como una consulta a una base de datos
Empieza por models y CRUD, añade relaciones, aprende a detectar N+1, utiliza eager loading y después profundiza en scopes, paginación, transacciones y rendimiento. Cuanto mejor entiendas SQL, más potente será Eloquent en tus manos.
Continuar aprendiendo Laravel →