Comprender las relaciones entre modelos en Laravel Eloquent

Los modelos y sus relaciones son el corazón de Laravel Eloquent. Si te hacen pasar un mal rato o no eres capaz de encontrar una guía sencilla, amigable y completa, ¡comienza aquí!

Sentado al otro lado de su artículo de programación, es fácil para el escritor fingir o explotar el aura de experiencia/prestigio que brinda la plataforma. Pero seré honesto, lo pasé extremadamente mal aprendiendo laravel, aunque solo sea porque fue mi primer framework full-stack. Una razón fue que no lo estaba usando en el trabajo y lo estaba explorando por curiosidad; entonces, haría un intento, llegaría a un punto, me confundiría, me daría por vencido y finalmente lo olvidaría todo. Debo haber hecho esto 5-6 veces antes de que comenzara a tener sentido para mí (por supuesto, la documentación no ayuda).

Pero lo que aún no tenía sentido era Eloquent. O al menos, las relaciones entre modelos (porque Eloquent es demasiado grande para aprenderlo por completo). Los ejemplos de autores de modelos y publicaciones de blog son una broma porque los proyectos reales son mucho más complejos; lamentablemente, los documentos oficiales usan los mismos ejemplos (o similares). O incluso si encontré algún artículo/recurso útil, la explicación era tan mala o faltaba tanto que simplemente no sirvió de nada.

(Por cierto, me han atacado antes por atacar la documentación oficial, así que si tienes ideas similares, esta es mi respuesta estándar: revisa la documentación de Django y luego habla conmigo).

Eventualmente, poco a poco, se unió y tuvo sentido. Finalmente pude modelar proyectos correctamente y usar los modelos cómodamente. Entonces, un día, me encontré con algunos trucos geniales de Collections que hacen que este trabajo sea más agradable. En este artículo, tengo la intención de cubrirlo todo, comenzando desde lo básico y luego cubriendo todos los posibles casos de uso que encontrará en proyectos reales.

¿Por qué son difíciles las relaciones con modelos de Eloquent?

Lamentablemente, me encuentro con demasiados desarrolladores de Laravel que no entienden los modelos correctamente.

¿Pero por qué?

Incluso hoy, cuando hay una explosión de cursos, artículos y videos sobre Laravel, la comprensión general es deficiente. Creo que es un punto importante y vale la pena reflexionar.

Si me preguntas, te diré que las relaciones modelo Eloquent no son nada difíciles. Al menos visto desde la perspectiva de la definición de “duro”. Las migraciones de esquemas en vivo son difíciles; escribir un nuevo motor de plantillas es difícil; contribuir con código al núcleo de Laravel es difícil. En comparación con estos, aprender y usar un ORM. . . Bueno, ¡eso no puede ser difícil! 🤭🤭

Lo que realmente sucede es que los desarrolladores de PHP que aprenden Laravel encuentran difícil Eloquent. Ese es el verdadero problema subyacente y, en mi opinión, hay varios factores que contribuyen a esto (¡alerta de opinión dura e impopular!):

  • Antes de Laravel, la exposición a un marco para la mayoría de los desarrolladores de PHP ha sido CodeIgniter (todavía es viva, por cierto, incluso si se parece más a Laravel/CakePHP). En la comunidad más antigua de CodeIgniter (si había una), la «mejor práctica» era incluir consultas SQL directamente donde fuera necesario. Y aunque hoy tenemos un nuevo CodeIgniter, los hábitos se han mantenido. Como resultado, al aprender Laravel, la idea de un ORM es 100 % nueva para los desarrolladores de PHP.
  • Descartando el ínfimo porcentaje de PHP expuesto a frameworks como Yii, CakePHP, etc., el resto está acostumbrado a trabajar en core PHP o en un entorno como WordPress. Y aquí nuevamente, no existe una mentalidad basada en programación orientada a objetos, por lo que un marco, un contenedor de servicios, un patrón de diseño, un ORM. . . estos son conceptos extraños.
  • Hay poco o ningún concepto de aprendizaje continuo en el mundo de PHP. El desarrollador promedio está feliz de trabajar con configuraciones de un solo servidor utilizando bases de datos relacionales y emitiendo consultas escritas como cadenas. Programación asincrónica, sockets web, HTTP 2/3, Linux (olvídese de Docker), pruebas unitarias, diseño controlado por dominio: todas estas son ideas extrañas para una proporción abrumadora de desarrolladores de PHP. Como resultado, leer sobre algo nuevo y desafiante, hasta el punto en que uno lo encuentra cómodo, no sucede cuando se encuentra con Eloquent.
  • La comprensión general de las bases de datos y el modelado también es deficiente. Dado que el diseño de la base de datos está directa e inseparablemente vinculado a los modelos de Eloquent, eleva aún más el nivel de dificultad.

No quiero ser duro y generalizar a nivel mundial: también hay excelentes desarrolladores de PHP, y muchos de ellos, pero su porcentaje general es muy bajo.

Si estás leyendo esto, significa que has cruzado todas estas barreras, te has encontrado con Laravel y te has metido con Eloquent.

¡Felicidades! 👏

Ya casi estás ahí. Todos los bloques de construcción están en su lugar y solo tenemos que revisarlos en el orden y detalle adecuados. En otras palabras, comencemos en el nivel de la base de datos.

Modelos de bases de datos: relaciones y cardinalidad

Para mantener las cosas simples, supongamos que estamos trabajando solo con bases de datos relacionales a lo largo de este artículo. Una razón es que los ORM se desarrollaron originalmente para bases de datos relacionales; la otra razón es que los RDBMS siguen siendo abrumadoramente populares.

Modelo de datos

Primero, comprendamos mejor los modelos de datos. La idea de un modelo (o un modelo de datos, para ser más precisos), proviene de la base de datos. Sin base de datos, sin datos, y por lo tanto, sin modelo de datos. ¿Y qué es un modelo de datos? Sencillamente, es la forma en que decide almacenar/estructurar sus datos. Por ejemplo, en una tienda de comercio electrónico, puede almacenar todo en una tabla gigante (práctica HORRIBLE, pero lamentablemente, no es poco común en el mundo de PHP); ese sería tu modelo de datos. También puede dividir los datos en 20 tablas principales y 16 de conexión; eso también es un modelo de datos.

Además, tenga en cuenta que la forma en que se estructuran los datos en la base de datos no necesita coincidir al 100 % con la forma en que se organizan en el ORM del marco. Sin embargo, el esfuerzo siempre es mantener las cosas lo más cerca posible para que no tengamos una cosa más que tener en cuenta al desarrollar.

Cardinalidad

También eliminemos este término rápido: cardinalidad. Simplemente se refiere a «contar», en términos generales. Entonces, 1, 2, 3. . . todo puede ser la cardinalidad de algo. Fin de la historia. ¡Sigamos moviéndonos!

Relaciones

Ahora, cada vez que almacenamos datos en cualquier tipo de sistema, hay formas en que los puntos de datos pueden relacionarse entre sí. Sé que esto suena abstracto y aburrido, pero tengan un poco de paciencia conmigo. Las formas en que se conectan los diferentes elementos de datos se conocen como relaciones. Veamos primero algunos ejemplos que no son de base de datos para que estemos convencidos de que entendemos completamente la idea.

  • Si almacenamos todo en una matriz, una posible relación es: el siguiente elemento de datos está en un índice mayor que el índice anterior en 1.
  • Si almacenamos datos en un árbol binario, una relación posible es que el árbol secundario de la izquierda siempre tenga valores más pequeños que los del nodo principal (si elegimos mantener el árbol de esa manera).
  • Si almacenamos datos como una matriz de matrices de igual longitud, podemos imitar una matriz y luego sus propiedades se convierten en las relaciones de nuestros datos.

Entonces vemos que la palabra “relación”, en el contexto de datos, no tiene un significado fijo. De hecho, si dos personas estuvieran mirando los mismos datos, podrían identificar dos relaciones de datos muy diferentes (¡hola, estadísticas!) y ambas podrían ser válidas.

Bases de datos relacionales

Con base en todos los términos que hemos discutido hasta ahora, finalmente podemos hablar de algo que tiene un vínculo directo con los modelos en un marco web (Laravel): bases de datos relacionales. Para la mayoría de nosotros, la base de datos principal utilizada es MySQL, MariaDB, PostgreSQL, MSSQL, SQL Server, SQLite o algo por el estilo. También es posible que sepamos vagamente que estos se llaman RDBMS, pero la mayoría de nosotros hemos olvidado lo que realmente significa y por qué es importante.

La «R» en RDBMS significa relacional, por supuesto. Este no es un término elegido arbitrariamente; por esto, destacamos el hecho de que estos sistemas de bases de datos están diseñados para trabajar de manera eficiente con las relaciones entre los datos almacenados. De hecho, «relación» aquí tiene un significado matemático estricto, y aunque ningún desarrollador necesita preocuparse por eso, es útil saber que existe una relación matemática rigurosa. Fundación debajo de este tipo de bases de datos.

Explore estos recursos para aprender SQL y NoSQL.

Bien, sabemos por experiencia que los datos en RDBMS se almacenan como tablas. ¿Dónde, entonces, están las relaciones?

Tipos de relaciones en RDBMS

Esta es quizás la parte más importante de todo el tema de Laravel y las relaciones entre modelos. Si no entiende esto, Eloquent nunca tendrá sentido, así que preste atención durante los próximos minutos (ni siquiera es tan difícil).

Un RDBMS nos permite tener relaciones entre datos, a nivel de base de datos. Esto significa que estas relaciones no son impracticables/imaginarias/subjetivas y pueden ser creadas o inferidas por diferentes personas con el mismo resultado.

Al mismo tiempo, existen ciertas capacidades/herramientas dentro de un RDBMS que nos permiten crear y hacer cumplir estas relaciones, tales como:

  • Clave primaria
  • Clave externa
  • Restricciones

No quiero que este artículo se convierta en un curso de bases de datos, así que asumiré que conoces estos conceptos. Si no es así, o en caso de que su confianza se sienta inestable, le recomiendo este video amigable (siéntase libre de explorar la serie completa):

Da la casualidad de que estas relaciones de estilo RDBMS también son las más comunes que ocurren en aplicaciones del mundo real (no siempre, ya que una red social se modela mejor como un gráfico y no como una colección de tablas). Entonces, echemos un vistazo a ellos uno por uno y también tratemos de entender dónde pueden ser útiles.

Relación uno a uno

En casi todas las aplicaciones web, hay cuentas de usuario. Además, lo siguiente es cierto (en términos generales) sobre los usuarios y las cuentas:

  • Un usuario solo puede tener una cuenta.
  • Una cuenta solo puede ser propiedad de un usuario.

Sí, podemos argumentar que una persona puede registrarse con otro correo electrónico y así crear dos cuentas, pero desde la perspectiva de la aplicación web, esas son dos personas diferentes con dos cuentas diferentes. La aplicación no mostrará, por ejemplo, los datos de una cuenta en otra.

Lo que significa toda esta sutileza es que si tiene una situación como esta en su aplicación y está utilizando una base de datos relacional, necesitaría diseñarla como una relación uno a uno. Tenga en cuenta que nadie lo está obligando artificialmente: hay una situación clara en el dominio comercial y está utilizando una base de datos relacional. . . sólo cuando se cumplen estas dos condiciones, se llega a una relación de uno a uno.

Para este ejemplo (usuarios y cuentas), así es como podemos implementar esta relación al crear el esquema:

CREATE TABLE users(
    id INT NOT NULL AUTO_INCREMENT,
    email VARCHAR(100) NOT NULL,
    password VARCHAR(100) NOT NULL,
    PRIMARY KEY(id)
);

CREATE TABLE accounts(
    id INT NOT NULL AUTO_INCREMENT,
    role VARCHAR(50) NOT NULL,
    PRIMARY KEY(id),
    FOREIGN KEY(id) REFERENCES users(id)
);

¿Notas el truco aquí? Es bastante poco común cuando se crean aplicaciones en general, pero en la tabla de cuentas, ¡tenemos la identificación del campo configurada como clave principal y clave externa! La propiedad de clave externa lo vincula a la tabla de usuarios (por supuesto 🙄), mientras que la propiedad de clave principal hace que la columna de identificación sea única: ¡una verdadera relación uno a uno!

Por supuesto, la fidelidad de esta relación no está garantizada. Por ejemplo, nada me impide agregar 200 nuevos usuarios sin agregar una sola entrada a la tabla de cuentas. ¡Si hago eso, termino con una relación de uno a cero! 🤭🤭 Pero dentro de los límites de la estructura pura, eso es lo mejor que podemos hacer. Si queremos evitar agregar usuarios sin cuentas, necesitamos la ayuda de algún tipo de lógica de programación, ya sea en forma de disparadores de bases de datos o validaciones impuestas por Laravel.

Si estás empezando a estresarte, tengo un muy buen consejo:

  • Tomar con calma. Tan lento como necesites. En lugar de tratar de terminar este artículo y los otros 15 que ha marcado para hoy, apéguese a este. Deje que tome 3, 4, 5 días si eso es lo que toma: su objetivo debe ser eliminar las relaciones modelo Eloquent de su lista para siempre. Ha saltado de un artículo a otro antes, desperdiciando varios cientos de horas y, sin embargo, no sirvió de nada. Entonces, haz algo diferente esta vez. 😇
  • Si bien este artículo trata sobre Laravel Eloquent, todo eso viene mucho más tarde. La base de todo es el esquema de la base de datos, por lo que nuestro enfoque debe ser hacerlo bien primero. Si no puede trabajar únicamente a nivel de base de datos (suponiendo que no haya marcos en el mundo), los modelos y las relaciones nunca tendrán pleno sentido. Entonces, olvídate de Laravel por ahora. Completamente. Solo estamos hablando y haciendo diseño de base de datos por ahora. Sí, haré referencias a Laravel de vez en cuando, pero tu trabajo es ignorarlas por completo si te complican la situación.
  • Más adelante, lea un poco más sobre las bases de datos y lo que ofrecen. Índices, rendimiento, activadores, estructuras de datos subyacentes y su comportamiento, almacenamiento en caché, relaciones en MongoDB. . . cualquier tema tangencial que pueda cubrir lo ayudará como ingeniero. Recuerde, los modelos de marco son solo caparazones fantasma; la funcionalidad real de una plataforma proviene de sus bases de datos subyacentes.

Relación de uno a muchos

No estoy seguro si te diste cuenta de esto, pero este es el tipo de relación que todos creamos intuitivamente en nuestro trabajo diario. Cuando creamos una tabla de pedidos (un ejemplo hipotético), por ejemplo, para almacenar una clave externa en la tabla de usuarios, creamos una relación de uno a muchos entre los usuarios y los pedidos. ¿Porqué es eso? Bueno, míralo de nuevo desde la perspectiva de quién puede tener cuántos: un usuario puede tener más de un pedido, que es más o menos cómo funciona todo el comercio electrónico. Y visto desde el lado opuesto, la relación dice que un pedido solo puede pertenecer a un usuario, lo que también tiene mucho sentido.

En el modelado de datos, los libros RDBMS y la documentación del sistema, esta situación se representa en forma de diagrama de la siguiente manera:

¿Observe las tres líneas que forman una especie de tridente? Este es el símbolo de «muchos», por lo que este diagrama dice que un usuario puede tener muchos pedidos.

Por cierto, estos conteos de «muchos» y «uno» que nos encontramos repetidamente son lo que se llama la Cardinalidad de una relación (¿recuerdan esta palabra de una sección anterior?). Nuevamente, para este artículo, el término no tiene uso, pero es útil conocer el concepto en caso de que surja durante las entrevistas o lecturas adicionales.

Sencillo, ¿verdad? Y en términos de SQL real, crear esta relación también es simple. De hecho, ¡es mucho más simple que el caso de una relación uno a uno!

CREATE TABLE users( 
    id INT NOT NULL AUTO_INCREMENT, 
    email VARCHAR(100) NOT NULL, 
    password VARCHAR(100) NOT NULL, 
    PRIMARY KEY(id) 
);

CREATE TABLE orders( 
    id INT NOT NULL AUTO_INCREMENT, 
    user_id INT NOT NULL, 
    description VARCHAR(50) NOT NULL, 
    PRIMARY KEY(id), 
    FOREIGN KEY(user_id) REFERENCES users(id) 
);

La tabla de pedidos almacena ID de usuario para cada pedido. Dado que no existe una restricción (restricción) de que las ID de usuario en la tabla de pedidos tengan que ser únicas, significa que podemos repetir una única ID muchas veces. Esto es lo que crea la relación de uno a muchos, y no una magia arcana que se esconde debajo. Los ID de usuario se almacenan de forma tonta en la tabla de pedidos, y SQL no tiene ningún concepto de uno a muchos, uno a uno, etc. Pero una vez que almacenamos datos de esta manera, puede pensar que hay una relación de uno a muchos.

Con suerte, tiene sentido ahora. O al menos, más sentido que antes. 😅 Recuerda que, como cualquier otra cosa, esto es una mera cuestión de práctica, y una vez que lo hayas hecho 4 o 5 veces en situaciones del mundo real, ni siquiera lo pensarás.

Relaciones de muchos a muchos

El siguiente tipo de relación que surge en la práctica es la llamada relación de muchos a muchos. Una vez más, antes de preocuparnos por los marcos o incluso sumergirnos en las bases de datos, pensemos en un análogo del mundo real: libros y autores. Piensa en tu autor favorito; han escrito más de un libro, ¿verdad? Al mismo tiempo, es bastante común ver a varios autores colaborando en un libro (al menos en el género de no ficción). Entonces, un autor puede escribir muchos libros y muchos autores pueden escribir un libro. Entre las dos entidades (libro y autor), esto forma una relación de muchos a muchos.

Ahora, dado que es muy poco probable que cree una aplicación del mundo real que involucre bibliotecas o libros y autores, pensemos en algunos ejemplos más. En un entorno B2B, un fabricante pide artículos a un proveedor y, a su vez, recibe una factura. La factura contendrá varias partidas, cada una de las cuales enumerará la cantidad y el artículo suministrado; por ejemplo, piezas de tubería de 5 pulgadas x 200, etc. En esta situación, los artículos y las facturas tienen una relación de muchos a muchos (piénselo y convénzase). En un sistema de gestión de flotas, los vehículos y los conductores tendrán una relación similar. En un sitio de comercio electrónico, los usuarios y los productos pueden tener una relación de muchos a muchos si consideramos funcionalidades como favoritos o listas de deseos.

Bastante justo, ahora, ¿cómo crear esta relación de muchos a muchos en SQL? Según nuestro conocimiento de cómo funciona la relación de uno a muchos, podría ser tentador pensar que deberíamos almacenar claves externas para la otra tabla en ambas tablas. Sin embargo, nos encontramos con grandes problemas si tratamos de hacer esto. Eche un vistazo a este ejemplo en el que se supone que los libros y los autores tienen una relación de muchos a muchos:

A primera vista, todo parece estar bien: los libros se asignan a los autores exactamente en una forma de muchos a muchos. Pero mire de cerca los datos de la tabla de autores: los ID de libro 12 y 13 están escritos por Peter M. (ID de autor 2), por lo que no tenemos más remedio que repetir las entradas. No solo la tabla de autores ahora tiene problemas de integridad de datos (adecuada normalización y todo eso), los valores en la columna id ahora se repiten. Esto significa que en el diseño que hemos elegido, no puede haber una columna de clave principal (porque las claves principales no pueden tener valores duplicados), y todo se desmorona.

Claramente, necesitamos una nueva forma de hacer esto y, afortunadamente, este problema ya se ha resuelto. Dado que almacenar claves foráneas directamente en ambas tablas arruina las cosas, la forma correcta de crear relaciones de muchos a muchos en RDBMS es creando una llamada «tabla de unión». La idea es básicamente dejar que las dos tablas originales permanezcan intactas y crear una tercera tabla para demostrar el mapeo de muchos a muchos.

Rehagamos el ejemplo fallido para que contenga una tabla de unión:

Tenga en cuenta que ha habido cambios drásticos:

  • Se reduce el número de columnas en la tabla de autores.
  • Se reduce el número de columnas en la tabla de libros.
  • El número de filas en la tabla de autores se reduce porque ya no es necesario repetir.
  • Apareció una nueva tabla llamada author_books, que contiene información sobre qué id de autor está conectado a qué id de libro. Podríamos haberle dado cualquier nombre a la tabla de unión, pero por convención es el resultado de simplemente unir las dos tablas que representa, usando un guión bajo.

La tabla de unión no tiene clave principal y, en la mayoría de los casos, contiene solo dos columnas: ID de las dos tablas. Es casi como si hubiésemos eliminado las columnas de clave externa de nuestro ejemplo anterior y las hubiéramos pegado en esta nueva tabla. Dado que no hay una clave principal, puede haber tantas repeticiones como sea necesario para registrar todas las relaciones.

Ahora, podemos ver con nuestros ojos cómo la tabla de unión muestra las relaciones claramente, pero ¿cómo accedemos a ellas en nuestras aplicaciones? El secreto está vinculado al nombre: tabla de unión. Este no es un curso sobre consultas SQL, por lo que no me sumergiré en él, pero la idea es que si desea todos los libros de un autor en particular en una consulta eficiente, SQL une las tablas en el mismo orden -> autores, autores_libros y libros. Las tablas autores y libros_autores se unen en las columnas id y id_autor, respectivamente, mientras que las tablas libros_autores y libros se unen en las columnas id_libro e id, respectivamente.

Agotador, eso sí. Pero mire el lado positivo: hemos terminado toda la teoría y el trabajo preliminar necesarios que teníamos que hacer antes de abordar los modelos Eloquent. ¡Y déjame recordarte que todo esto no es opcional! No saber el diseño de la base de datos lo dejará en la tierra de la confusión Eloquent para siempre. Además, cualquier cosa que Eloquent haga o intente hacer, refleja perfectamente estos detalles a nivel de la base de datos, por lo que es fácil ver por qué tratar de aprender Eloquent mientras se huye de RDBMS es un ejercicio inútil.

Creando relaciones de modelo en Laravel Eloquent

Finalmente, después de un desvío que duró unas 70,000 millas, llegamos al punto en el que podemos hablar sobre Eloquent, sus modelos y cómo crearlos/usarlos. Ahora, aprendimos en la parte anterior del artículo que todo comienza con la base de datos y cómo modela sus datos. Esto me hizo darme cuenta de que debería usar un solo ejemplo completo donde comenzar un nuevo proyecto. Al mismo tiempo, quiero que este ejemplo sea del mundo real, y no de blogs y autores o libros y estantes (que también son del mundo real, pero se han hecho hasta la saciedad).

Imaginemos una tienda que vende peluches. Supongamos también que se nos ha proporcionado el documento de requisitos, a partir del cual podemos identificar estas cuatro entidades en el sistema: usuarios, pedidos, facturas, artículos, categorías, subcategorías y transacciones. Sí, es probable que haya más complicaciones involucradas, pero dejemos eso de lado y concentrémonos en cómo pasamos de un documento a una aplicación.

Una vez que se han identificado las entidades principales del sistema, debemos pensar en cómo se relacionan entre sí, en términos de las relaciones de la base de datos que hemos discutido hasta ahora. Estos son los que se me ocurren:

  • Usuarios y Pedidos: Uno a muchos.
  • Pedidos y facturas: Uno a uno. Me doy cuenta de que esto no está cortado y seco, y dependiendo de su dominio comercial, puede haber una relación de uno a muchos, de muchos a uno o de muchos a muchos. Pero cuando se trata de su pequeña tienda de comercio electrónico promedio, un pedido solo resultará en una factura y viceversa.
  • Órdenes y Artículos: Muchos a muchos.
  • Artículos y Categorías: Muchos a uno. Nuevamente, esto no es así en los grandes sitios de comercio electrónico, pero tenemos una operación pequeña.
  • Categorías y Subcategorías: de uno a muchos. Nuevamente, encontrará la mayoría de los ejemplos del mundo real que contradicen esto, pero bueno, Eloquent es lo suficientemente difícil como es, ¡así que no hagamos que el modelado de datos sea más difícil!
  • Órdenes y Transacciones: Uno a muchos. También me gustaría agregar estos dos puntos como justificación de mi elección: 1) Podríamos haber agregado una relación entre Transacciones y Facturas también. Es solo una decisión de modelado de datos. 2) ¿Por qué uno a muchos aquí? Bueno, es común que el pago de un pedido falle por alguna razón y tenga éxito la próxima vez. En este caso, tenemos dos transacciones creadas para ese pedido. Si deseamos mostrar esas transacciones fallidas o no es una decisión comercial, pero siempre es una buena idea capturar datos valiosos.

¿Existen otras relaciones? Bueno, muchas más relaciones son posibles, pero no son prácticas. Por ejemplo, podemos decir que un usuario tiene muchas transacciones, por lo que debe existir una relación entre ellas. Lo que hay que darse cuenta aquí es que ya existe una relación indirecta: usuarios -> pedidos -> transacciones y, en general, es lo suficientemente bueno como RDBMS son bestias en la unión de tablas. En segundo lugar, crear esta relación significaría agregar una columna user_id a la tabla de transacciones. Si hiciéramos esto para todas las relaciones directas posibles, estaríamos agregando mucha más carga a la base de datos (en forma de más almacenamiento, especialmente si se usan UUID y manteniendo índices), encadenando el sistema general. Claro, si la empresa dice que necesita datos de transacciones y los necesita en 1,5 segundos, podríamos decidir agregar esa relación y acelerar las cosas (concesiones, concesiones…).

Y ahora, damas y caballeros, ¡ha llegado el momento de escribir el código real!

Relaciones de modelos de Laravel: ejemplo real con código

La siguiente fase de este artículo trata de ensuciarse las manos, pero de una manera útil. Recogeremos las mismas entidades de la base de datos que en el ejemplo anterior de comercio electrónico y veremos cómo se crean y conectan los modelos en Laravel, ¡directamente desde la instalación de Laravel!

Naturalmente, supongo que tiene configurado su entorno de desarrollo y sabe cómo instalar y usar Composer para administrar las dependencias.

$ composer global require laravel/installer -W
$ laravel new model-relationships-study

Estos dos comandos de consola instalan el instalador de Laravel (la parte -W se usa para actualizar ya que ya tenía instalada una versión anterior). Y en caso de que tenga curiosidad, al momento de escribir, la versión de Laravel que se instaló es 8.5.9. ¿Deberías entrar en pánico y actualizar también? Lo desaconsejaría, ya que no espero cambios importantes entre Laravel 5 y Laravel 8 en el contexto de nuestra aplicación. Algunas cosas han cambiado y tendrán un impacto en este artículo (como las fábricas de modelos), pero creo que podrá portar el código.

Dado que ya hemos analizado el modelo de datos y sus relaciones, la parte de crear los modelos será trivial. Y también verá (¡estoy sonando como un disco rayado ahora!) cómo refleja el esquema de la base de datos, ya que depende al 100% de él.

En otras palabras, primero debemos crear las migraciones (y los archivos de modelo) para todos los modelos, que se aplicarán a la base de datos. Más tarde, podemos trabajar en los modelos y agregar las relaciones.

Entonces, ¿con qué modelo empezamos? El más simple y el menos conectado, por supuesto. En nuestro caso, esto significa el modelo de Usuario. Dado que Laravel se envía con este modelo (y no puede funcionar sin él 🤣), modifiquemos el archivo de migración y también limpiemos el modelo para que se adapte a nuestras necesidades simples.

Aquí está la clase de migración:

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
        });
    }
}

Dado que en realidad no estamos construyendo un proyecto, no necesitamos entrar en contraseñas, is_active y todo eso. Nuestra tabla de usuarios tendrá solo dos columnas: la identificación y el nombre del usuario.

Vamos a crear la migración para Categoría a continuación. Dado que Laravel nos permite la conveniencia de generar el modelo también en un solo comando, aprovecharemos eso, aunque no tocaremos el archivo del modelo por ahora.

$ php artisan make:model Category -m
Model created successfully.
Created Migration: 2021_01_26_093326_create_categories_table

Y aquí está la clase de migración:

class CreateCategoriesTable extends Migration
{
    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
        });
    }
}

Si está sorprendido por la ausencia de la función down(), no lo esté; en la práctica, rara vez termina utilizándolo, ya que eliminar una columna o tabla o cambiar un tipo de columna da como resultado la pérdida de datos que no se pueden recuperar. En el desarrollo, se encontrará descartando toda la base de datos y luego volviendo a ejecutar las migraciones. Pero estamos divagando, así que regresemos y abordemos la siguiente entidad. Dado que las subcategorías están directamente relacionadas con las categorías, creo que es una buena idea hacer eso a continuación.

$ php artisan make:model SubCategory -m
Model created successfully.
Created Migration: 2021_01_26_140845_create_sub_categories_table

Muy bien, ahora completemos el archivo de migración:

class CreateSubCategoriesTable extends Migration
{
    public function up()
    {
        Schema::create('sub_categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');

            $table->unsignedBigInteger('category_id');
            $table->foreign('category_id')
                ->references('id')
                ->on('categories')
                ->onDelete('cascade');
        });
    }
}

Como puede ver, agregamos una columna separada aquí, llamada category_id, que almacenará las ID de la tabla de categorías. No hay premios por adivinar, esto crea una relación de uno a muchos a nivel de base de datos.

Ahora es el turno de los artículos:

$ php artisan make:model Item -m
Model created successfully.
Created Migration: 2021_01_26_141421_create_items_table

Y la migración:

class CreateItemsTable extends Migration
{
    public function up()
    {
        Schema::create('items', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->text('description');
            $table->string('type');
            $table->unsignedInteger('price');
            $table->unsignedInteger('quantity_in_stock');

            $table->unsignedBigInteger('sub_category_id');
            $table->foreign('sub_category_id')
                ->references('id')
                ->on('sub_categories')
                ->onDelete('cascade');
        });
    }
}

Si sientes que las cosas deben hacerse de otra manera, está bien. Dos personas rara vez encontrarán exactamente el mismo esquema y arquitectura. Tenga en cuenta una cosa, que es una práctica recomendada: he almacenado el precio como un número entero.

¿Por qué?

Bueno, la gente se dio cuenta de que manejar las divisiones flotantes y todo eso era feo y propenso a errores en el lado de la base de datos, por lo que comenzaron a almacenar el precio en términos de la unidad monetaria más pequeña. Por ejemplo, si estuviéramos operando en USD, el campo de precio aquí representaría centavos. En todo el sistema los valores y cálculos serán en centavos; solo cuando sea el momento de mostrar al usuario o enviar un PDF por correo electrónico, dividiremos por 100 y redondearemos. Inteligente, ¿eh?

De todos modos, observe que un elemento está vinculado a una subcategoría en una relación de muchos a uno. También está vinculado a una categoría. . . indirectamente a través de su subcategoría. Veremos demostraciones sólidas de todas estas gimnasias, pero por ahora, necesitamos apreciar los conceptos y asegurarnos de que estamos 100% claros.

El siguiente es el modelo Order y su migración:

$ php artisan make:model Order -m
Model created successfully.
Created Migration: 2021_01_26_144157_create_orders_table

En aras de la brevedad, incluiré solo algunos de los campos importantes en la migración. Con eso quiero decir que los detalles de un pedido pueden contener muchas cosas, pero los restringiremos a unos pocos para que podamos centrarnos en el concepto de relaciones modelo.

class CreateOrdersTable extends Migration
{
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->id();
            $table->string('status');
            $table->unsignedInteger('total_value');
            $table->unsignedInteger('taxes');
            $table->unsignedInteger('shipping_charges');

            $table->unsignedBigInteger('user_id');
            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->onDelete('cascade');
        });
    }
}

Se ve bien, pero, ¡espera un minuto! ¿Dónde están los artículos en este pedido? Como establecimos anteriormente, existe una relación de muchos a muchos entre pedidos y artículos, por lo que una clave externa simple no funciona. La solución es la llamada mesa de unión o mesa intermedia. En otras palabras, necesitamos una tabla de combinación para almacenar el mapeo de muchos a muchos entre pedidos y artículos. Ahora, en el mundo de Laravel, hay una convención incorporada que seguimos para ahorrar tiempo: si creo una nueva tabla usando la forma singular de los dos nombres de tabla, colóquelos en el orden del diccionario y únalos usando un guión bajo, Laravel lo reconocerá automáticamente como la tabla de unión.

En nuestro caso, la tabla de unión se llamará item_order (la palabra “item” viene antes de “order” en un diccionario). Además, como se explicó antes, esta tabla de unión normalmente contendrá solo dos columnas, claves externas para cada tabla.

Podríamos crear un modelo + migración aquí, pero el modelo nunca se usará ya que es más una meta meta. Por lo tanto, creamos una nueva migración en Laravel y le decimos qué es qué.

$ php artisan make:migration create_item_order_table --create="item_order"
Created Migration: 2021_01_27_093127_create_item_order_table

Esto da como resultado una nueva migración, que cambiaremos de la siguiente manera:

class CreateItemOrderTable extends Migration
{
    public function up()
    {
        Schema::create('item_order', function (Blueprint $table) {
            $table->unsignedBigInteger('order_id');
            $table->foreign('order_id')
                ->references('id')
                ->on('orders')
                ->onDelete('cascade');
            
            $table->unsignedBigInteger('item_id');
            $table->foreign('item_id')
                ->references('id')
                ->on('items')
                ->onDelete('cascade');    
        });
    }
}

Cómo acceder realmente a estas relaciones a través de llamadas a métodos Eloquent es un tema para más adelante, pero tenga en cuenta que primero debemos crear minuciosamente, a mano, estas claves externas. Sin estos, no hay Eloquent y no hay «inteligencia» en Laravel. 🙂

¿Ya llegamos? Bueno, casi . . .

Solo tenemos un par de modelos más de los que preocuparnos. La primera es la tabla de facturas, y recordará que decidimos convertirla en una relación uno a uno con los pedidos.

$ php artisan make:model Invoice -m
Model created successfully.
Created Migration: 2021_01_27_101116_create_invoices_table

En las primeras secciones de este artículo, vimos que una forma de imponer una relación uno a uno es hacer que la clave principal en la tabla secundaria también sea la clave externa. En la práctica, casi nadie adopta este enfoque excesivamente cauteloso y, por lo general, las personas diseñan el esquema como lo harían para una relación de uno a muchos. Mi opinión es que un enfoque intermedio es mejor; simplemente haga que la clave externa sea única y se haya asegurado de que las ID del modelo principal no se puedan repetir:

class CreateInvoicesTable extends Migration
{
    public function up()
    {
        Schema::create('invoices', function (Blueprint $table) {
            $table->id();
            $table->timestamp('raised_at')->nullable();
            $table->string('status');
            $table->unsignedInteger('totalAmount');

            $table->unsignedBigInteger('order_id')->unique();
            $table->foreign('order_id')
                ->references('id')
                ->on('orders')
                ->onDelete('cascade')
                ->unique();
        });
    }
}

Y sí, por enésima vez, soy consciente de que a esta tabla de facturas le faltan muchas cosas; sin embargo, nuestro enfoque aquí es ver cómo funcionan las relaciones del modelo y no diseñar una base de datos completa.

Bien, hemos llegado al punto en el que necesitamos crear la migración final de nuestro sistema (¡espero!). El enfoque ahora está en el modelo Transacción, que decidimos anteriormente que está vinculado al modelo Orden. Por cierto, aquí hay un ejercicio para ti: ¿Debería vincularse el modelo Transacción con el modelo Factura? ¿Por qué y por qué no? 🙂

$ php artisan make:model Transaction -m
Model created successfully.
Created Migration: 2021_01_31_145806_create_transactions_table

Y la migración:

class CreateTransactionsTable extends Migration
{
    public function up()
    {
        Schema::create('transactions', function (Blueprint $table) {
            $table->id();
            $table->timestamp('executed_at');
            $table->string('status');
            $table->string('payment_mode');
            $table->string('transaction_reference')->nullable();

            $table->unsignedBigInteger('order_id');
            $table->foreign('order_id')
                ->references('id')
                ->on('orders')
                ->onDelete('cascade');
        });
    }
}

¡Uf! Eso fue un trabajo duro. . . ejecutemos las migraciones y veamos cómo nos va a los ojos de la base de datos.

$ php artisan migrate:fresh
Dropped all tables successfully.
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (3.45ms)
Migrating: 2021_01_26_093326_create_categories_table
Migrated:  2021_01_26_093326_create_categories_table (2.67ms)
Migrating: 2021_01_26_140845_create_sub_categories_table
Migrated:  2021_01_26_140845_create_sub_categories_table (3.83ms)
Migrating: 2021_01_26_141421_create_items_table
Migrated:  2021_01_26_141421_create_items_table (6.09ms)
Migrating: 2021_01_26_144157_create_orders_table
Migrated:  2021_01_26_144157_create_orders_table (4.60ms)
Migrating: 2021_01_27_093127_create_item_order_table
Migrated:  2021_01_27_093127_create_item_order_table (3.05ms)
Migrating: 2021_01_27_101116_create_invoices_table
Migrated:  2021_01_27_101116_create_invoices_table (3.95ms)
Migrating: 2021_01_31_145806_create_transactions_table
Migrated:  2021_01_31_145806_create_transactions_table (3.54ms)

¡Alabado sea el Señor! 🙏🏻🙏🏻 Parece que hemos sobrevivido al momento del juicio.

Y con eso, ¡estamos listos para pasar a definir relaciones modelo! Para eso, debemos volver a la lista que creamos anteriormente, describiendo el tipo de relaciones directas entre modelos (tablas).

Para empezar, hemos establecido que existe una relación de uno a muchos entre los usuarios y los pedidos. Podemos confirmar esto yendo al archivo de migración de pedidos y viendo la presencia del campo user_id allí. Este campo es lo que crea la relación, porque cualquier relación que nos interese establecer primero debe ser respetada por la base de datos; el resto (sintaxis elocuente y dónde escribir qué función) es pura formalidad.

En otras palabras, la relación ya está ahí. Solo necesitamos decirle a Eloquent que esté disponible en tiempo de ejecución. Comencemos con el modelo Order, donde declaramos que pertenece al modelo User:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Order extends Model
{
    use HasFactory;

    public function user() {
        return $this->belongsTo(User::class);
    }
}

La sintaxis debe resultarle familiar; declaramos una función llamada usuario(), que sirve para acceder al usuario propietario de esta orden (el nombre de la función puede ser cualquier cosa; lo que devuelve es lo que importa). Vuelva a pensar por un momento: si no hubiera una base de datos ni claves externas, una declaración como $this->belongsTo no tendría sentido. Es solo porque hay una clave externa en la tabla de pedidos que Laravel puede usar ese ID de usuario para buscar al usuario con el mismo ID y devolverlo. Por sí mismo, sin la cooperación de la base de datos, Laravel no puede crear relaciones de la nada.

Ahora, también sería bueno poder escribir $usuario->pedidos para acceder a los pedidos de un usuario. Esto significa que debemos ir al modelo de usuario y escribir una función para la parte «varios» de esta relación de uno a varios:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class User extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function orders() {
        return $this->hasMany(Order::class);
    }
}

Sí, modifiqué mucho el modelo de usuario predeterminado porque no necesitamos todas las demás funciones para este tutorial. De todos modos, la clase User ahora tiene un método llamado orders(), que dice que un usuario puede estar asociado con múltiples pedidos. En el mundo ORM, decimos que la relación orders() aquí es la inversa de la relación user() que teníamos en el modelo Order.

Pero, ¡espera un minuto! ¿Cómo funciona esta relación? Quiero decir, no hay nada en el nivel de la base de datos que tenga múltiples conexiones saliendo de la tabla de usuarios a la tabla de pedidos.

En realidad, existe una conexión y resulta que es suficiente por sí sola: ¡la referencia de clave externa almacenada en la tabla de pedidos! Esto es, cuando decimos algo como $usuario->pedidos, Laravel activa la función de pedidos() y al mirarla sabe que hay una clave externa en la tabla de pedidos. Luego, hace un tipo de SELECCIÓN * DESDE pedidos DONDE user_id = 23 y devuelve los resultados de la consulta como una colección. Por supuesto, el objetivo de tener un ORM es olvidarse de SQL, pero no debemos olvidar por completo que la base subyacente es el RDBMS que ejecuta consultas SQL.

A continuación, repasemos los modelos de pedidos y facturas, donde tenemos una relación uno a uno:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Order extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function user() {
        return $this->belongsTo(User::class);
    }

    public function invoice() {
        return $this->hasOne(Invoice::class);
    }
}

Y el modelo de factura:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Invoice extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function order() {
        return $this->belongsTo(Order::class);
    }
}

Tenga en cuenta que en el nivel de la base de datos, así como casi en el nivel de Eloquent, es una relación típica de uno a muchos; acabamos de agregar algunas comprobaciones para asegurarnos de que se mantenga uno a uno.

Ahora llegamos a otro tipo de relación: la de muchos a muchos entre pedidos y artículos. Recuerda que ya hemos creado una tabla intermedia llamada item_order que almacena el mapeo entre las claves primarias. Si todo esto se ha hecho correctamente, definir la relación y trabajar con ella es trivial. Según los documentos de Laravel, para definir una relación de muchos a muchos, sus métodos deben devolver una instancia de pertenencia a muchos ().

Entonces, en el modelo Item:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Item extends Model
{
    use HasFactory;
    public $timestamps = false;

    public function orders() {
        return $this->belongsToMany(Order::class);
    }
}

Sorprendentemente, la relación inversa es casi idéntica:

class Order extends Model
{
    /* ... other code */
    
    public function items() {
        return $this->belongsToMany(Item::class);
    }
}

¡Y eso es! Siempre que hayamos seguido las convenciones de nomenclatura correctamente, Laravel puede deducir las asignaciones y su ubicación.

Ya que se han cubierto los tres tipos fundamentales de relaciones (uno a uno, uno a muchos, muchos a muchos), dejaré de escribir los métodos para otros modelos, ya que estarán a lo largo del mismas líneas. En cambio, ¡creemos las fábricas para estos modelos, creemos algunos datos ficticios y veamos estas relaciones en acción!

¿Como hacemos eso? Bueno, tomemos el camino rápido y sucio y coloquemos todo en el archivo seeder predeterminado. Luego, cuando ejecutemos las migraciones, también ejecutaremos la sembradora. Entonces, así es como se ve mi archivo DatabaseSeeder.php:

<?php

namespace DatabaseSeeders;

use IlluminateDatabaseSeeder;
use AppModelsCategory;
use AppModelsSubCategory;
use AppModelsItem;
use AppModelsOrder;
use AppModelsInvoice;
use AppModelsUser;
use Faker;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $faker = FakerFactory::create();

        // Let's make two users
        $user1 = User::create(['name' => $faker->name]);
        $user2 = User::create(['name' => $faker->name]);

        // Create two categories, each having two subcategories
        $category1 = Category::create(['name' => $faker->word]);
        $category2 = Category::create(['name' => $faker->word]);

        $subCategory1 = SubCategory::create(['name' => $faker->word, 'category_id' => $category1->id]);
        $subCategory2 = SubCategory::create(['name' => $faker->word, 'category_id' => $category1->id]);

        $subCategory3 = SubCategory::create(['name' => $faker->word, 'category_id' => $category2->id]);
        $subCategory4 = SubCategory::create(['name' => $faker->word, 'category_id' => $category2->id]);

        // After categories, well, we have items
        // Let's create two items each for sub-category 2 and 4
        $item1 = Item::create([
            'sub_category_id' => 2,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(2),
            'quantity_in_stock' => $faker->randomNumber(2),
        ]);

        $item2 = Item::create([
            'sub_category_id' => 2,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(3),
            'quantity_in_stock' => $faker->randomNumber(2),
        ]);

        $item3 = Item::create([
            'sub_category_id' => 4,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(4),
            'quantity_in_stock' => $faker->randomNumber(2),
        ]);

        $item4 = Item::create([
            'sub_category_id' => 4,
            'name' => $faker->name,
            'description' => $faker->text,
            'type' => $faker->word,
            'price' => $faker->randomNumber(1),
            'quantity_in_stock' => $faker->randomNumber(3),
        ]);

        // Now that we have users and items, let's make user1 place a couple of orders
        $order1 = Order::create([
            'status' => 'confirmed',
            'total_value' => $faker->randomNumber(3),
            'taxes' => $faker->randomNumber(1),
            'shipping_charges' => $faker->randomNumber(2),
            'user_id' => $user1->id
        ]);

        $order2 = Order::create([
            'status' => 'waiting',
            'total_value' => $faker->randomNumber(3),
            'taxes' => $faker->randomNumber(1),
            'shipping_charges' => $faker->randomNumber(2),
            'user_id' => $user1->id
        ]);

        // now, assigning items to orders
        $order1->items()->attach($item1);
        $order1->items()->attach($item2);
        $order1->items()->attach($item3);
        
        $order2->items()->attach($item1);
        $order2->items()->attach($item4);

        // and finally, create invoices
        $invoice1 = Invoice::create([
            'raised_at' => $faker->dateTimeThisMonth(),
            'status' => 'settled',
            'totalAmount' => $faker->randomNumber(3),
            'order_id' => $order1->id,
        ]);
    }
}

Y ahora volvemos a configurar la base de datos y la inicializamos:

$ php artisan migrate:fresh --seed
Dropped all tables successfully.
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (43.81ms)
Migrating: 2021_01_26_093326_create_categories_table
Migrated:  2021_01_26_093326_create_categories_table (2.20ms)
Migrating: 2021_01_26_140845_create_sub_categories_table
Migrated:  2021_01_26_140845_create_sub_categories_table (4.56ms)
Migrating: 2021_01_26_141421_create_items_table
Migrated:  2021_01_26_141421_create_items_table (5.79ms)
Migrating: 2021_01_26_144157_create_orders_table
Migrated:  2021_01_26_144157_create_orders_table (6.40ms)
Migrating: 2021_01_27_093127_create_item_order_table
Migrated:  2021_01_27_093127_create_item_order_table (4.66ms)
Migrating: 2021_01_27_101116_create_invoices_table
Migrated:  2021_01_27_101116_create_invoices_table (6.70ms)
Migrating: 2021_01_31_145806_create_transactions_table
Migrated:  2021_01_31_145806_create_transactions_table (6.09ms)
Database seeding completed successfully.

¡Está bien! Ahora es la parte final de este artículo, donde simplemente accedemos a estas relaciones y confirmamos todo lo que hemos aprendido hasta ahora. Estarás encantado de saber (eso espero) que esta será una sección ligera y divertida.

Y ahora, activemos el componente Laravel más divertido: ¡la consola interactiva Tinker!

$ php artisan tinker
Psy Shell v0.10.6 (PHP 8.0.0 — cli) by Justin Hileman
>>>

Acceso a relaciones de modelo uno a uno en Laravel Eloquent

Bien, primero, accedamos a la relación uno a uno que tenemos en nuestros modelos de pedido y factura:

>>> $order = Order::find(1);
[!] Aliasing 'Order' to 'AppModelsOrder' for this Tinker session.
=> AppModelsOrder {#4108
     id: 1,
     status: "confirmed",
     total_value: 320,
     taxes: 5,
     shipping_charges: 12,
     user_id: 1,
   }
>>> $order->invoice
=> AppModelsInvoice {#4004
     id: 1,
     raised_at: "2021-01-21 19:20:31",
     status: "settled",
     totalAmount: 314,
     order_id: 1,
   }

¿Notas algo? Recuerde que la forma en que se ha hecho a nivel de base de datos, esta relación es de uno a muchos, si no fuera por las restricciones adicionales. Entonces, Laravel podría haber devuelto una colección de objetos (o solo un objeto) como resultado, y eso sería técnicamente preciso. PERO . . . le hemos dicho a Laravel que es una relación de uno a uno, por lo que el resultado es una única instancia de Eloquent. Fíjate cómo sucede lo mismo al acceder a la relación inversa:

$invoice = Invoice::find(1);
[!] Aliasing 'Invoice' to 'AppModelsInvoice' for this Tinker session.
=> AppModelsInvoice {#3319
     id: 1,
     raised_at: "2021-01-21 19:20:31",
     status: "settled",
     totalAmount: 314,
     order_id: 1,
   }
>>> $invoice->order
=> AppModelsOrder {#4042
     id: 1,
     status: "confirmed",
     total_value: 320,
     taxes: 5,
     shipping_charges: 12,
     user_id: 1,
   }

Acceso a relaciones de modelo de uno a muchos en Laravel Eloquent

Tenemos una relación de uno a muchos entre los usuarios y los pedidos. Vamos a «jugar» con él ahora y ver el resultado:

>>> User::find(1)->orders;
[!] Aliasing 'User' to 'AppModelsUser' for this Tinker session.
=> IlluminateDatabaseEloquentCollection {#4291
     all: [
       AppModelsOrder {#4284
         id: 1,
         status: "confirmed",
         total_value: 320,
         taxes: 5,
         shipping_charges: 12,
         user_id: 1,
       },
       AppModelsOrder {#4280
         id: 2,
         status: "waiting",
         total_value: 713,
         taxes: 4,
         shipping_charges: 80,
         user_id: 1,
       },
     ],
   }
>>> Order::find(1)->user
=> AppModelsUser {#4281
     id: 1,
     name: "Dallas Kshlerin",
   }

Exactamente como se esperaba, acceder a los pedidos de un usuario da como resultado una colección de registros, mientras que lo contrario produce un solo objeto Usuario. En otras palabras, uno a muchos.

Acceso a relaciones de modelo de muchos a muchos en Laravel Eloquent

Ahora, exploremos una relación de muchos a muchos. Tenemos una de esas relaciones entre artículos y pedidos:

>>> $item1 = Item::find(1);
[!] Aliasing 'Item' to 'AppModelsItem' for this Tinker session.
=> AppModelsItem {#4253
     id: 1,
     name: "Russ Kutch",
     description: "Deserunt voluptatibus omnis ut cupiditate doloremque. Perspiciatis officiis odio et accusantium alias aut. Voluptatum provident aut ut et.",
     type: "adipisci",
     price: 26,
     quantity_in_stock: 65,
     sub_category_id: 2,
   }
>>> $order1 = Order::find(1);
=> AppModelsOrder {#4198
     id: 1,
     status: "confirmed",
     total_value: 320,
     taxes: 5,
     shipping_charges: 12,
     user_id: 1,
   }
>>> $order1->items
=> IlluminateDatabaseEloquentCollection {#4255
     all: [
       AppModelsItem {#3636
         id: 1,
         name: "Russ Kutch",
         description: "Deserunt voluptatibus omnis ut cupiditate doloremque. Perspiciatis officiis odio et accusantium alias aut. Voluptatum provident aut ut et.",
         type: "adipisci",
         price: 26,
         quantity_in_stock: 65,
         sub_category_id: 2,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4264
           order_id: 1,
           item_id: 1,
         },
       },
       AppModelsItem {#3313
         id: 2,
         name: "Mr. Green Cole",
         description: "Maxime beatae porro commodi fugit hic. Et excepturi natus distinctio qui sit qui. Est non non aut necessitatibus aspernatur et aspernatur et. Voluptatem possimus consequatur exercitationem et.",
         type: "pariatur",
         price: 381,
         quantity_in_stock: 82,
         sub_category_id: 2,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4260
           order_id: 1,
           item_id: 2,
         },
       },
       AppModelsItem {#4265
         id: 3,
         name: "Brianne Weissnat IV",
         description: "Delectus ducimus quia voluptas fuga sed eos esse. Rerum repudiandae incidunt laboriosam. Ea eius omnis autem. Cum pariatur aut voluptas sint aliquam.",
         type: "non",
         price: 3843,
         quantity_in_stock: 26,
         sub_category_id: 4,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4261
           order_id: 1,
           item_id: 3,
         },
       },
     ],
   }
>>> $item1->orders
=> IlluminateDatabaseEloquentCollection {#4197
     all: [
       AppModelsOrder {#4272
         id: 1,
         status: "confirmed",
         total_value: 320,
         taxes: 5,
         shipping_charges: 12,
         user_id: 1,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4043
           item_id: 1,
           order_id: 1,
         },
       },
       AppModelsOrder {#4274
         id: 2,
         status: "waiting",
         total_value: 713,
         taxes: 4,
         shipping_charges: 80,
         user_id: 1,
         pivot: IlluminateDatabaseEloquentRelationsPivot {#4257
           item_id: 1,
           order_id: 2,
         },
       },
     ],
   }

Esta salida puede ser un poco mareante de leer, pero observe que item1 es parte de los elementos de order1, y viceversa, que es como configuramos las cosas. También echemos un vistazo a la tabla intermedia que almacena las asignaciones:

>>> use DB;
>>> DB::table('item_order')->select('*')->get();
=> IlluminateSupportCollection {#4290
     all: [
       {#4270
         +"order_id": 1,
         +"item_id": 1,
       },
       {#4276
         +"order_id": 1,
         +"item_id": 2,
       },
       {#4268
         +"order_id": 1,
         +"item_id": 3,
       },
       {#4254
         +"order_id": 2,
         +"item_id": 1,
       },
       {#4267
         +"order_id": 2,
         +"item_id": 4,
       },
     ],
   }

Conclusión

¡Sí, esto es todo, de verdad! Ha sido un artículo muy largo, pero espero que haya sido útil. ¿Es eso todo lo que uno necesita saber sobre los modelos de Laravel?

Tristemente no. La madriguera del conejo es muy, muy profunda, y hay muchos conceptos más desafiantes, como las relaciones polimórficas y el ajuste del rendimiento, y demás, que encontrarás a medida que crezcas como desarrollador de Laravel. Por ahora, lo que cubre este artículo es suficiente para el 70% de los desarrolladores el 70% del tiempo, en términos generales. Pasará mucho tiempo antes de que sienta la necesidad de actualizar sus conocimientos.

Con esa advertencia fuera del camino, quiero que se lleve esta idea más importante: nada es magia oscura o está fuera del alcance en la programación. Es solo que no entendemos los cimientos y cómo se construyen las cosas, lo que nos hace luchar y sentirnos frustrados.

Asi que . . . ?

¡Invierte en ti mismo! Cursos, libros, artículos, otras comunidades de programación (Python es mi recomendación n. ° 1): use todos los recursos que pueda encontrar y consúmalos regularmente, aunque lentamente. Muy pronto, la cantidad de instancias en las que es probable que falles todo disminuirá drásticamente.

Bien, basta de sermones. ¡Que tengas un lindo día! 🙂