Tabla de Contenidos
Relaciones entre modelos
Relaciones uno a uno
Por ejemplo, un pago pertenece a una orden y una orden tiene un único pago.
Para estas relaciones se necesitan claves foráneas.
El modelo que pertenece a otro es el que lleva la clave foránea. Para el ejemplo, anterior, un pago pertenece a una orden, así que será el pago quien lleve la clave foránea de la orden.
Por consistencia, se recomienda que la clave foránea se llame igual que el modelo relacionado seguido de _id.
Vamos la migración donde se crea un orden:
// code class createPaymentsTable extends Migration { public function up() { Schema::create("orders", function (Blueprint $table) { $table->id(); $table->float("amount")->unsigned(); $table->timestamp("payed_at")->nullable(); $table->bigInteger("order_id")->unsigned(); $table->timestamps(); // Establecemos la relación de la clave foránea con la clave // primaria de la tabla padre $table->foreign("order_id")->references("id")->on("orders"); }); } // code }
Ahora tenemos que relacionar a nivel de modelo las órdenes y los pagos. Vamos al model Order.php:
namespace App; use App\Model\Payment; use Illuminate\Database\Eloquent\Model; class Order extends Model { protected $fillable = ["status"]; public function payment() { // Una orden tiene un pago return $this->hasOne(Payment::class); } }
Ahora tenemos que ir al modelo Payment.php para poder decir que pertenece a una orden:
namespace App; use App\Models\Order; // code protected $fillable = [ "amount", "payed_at", "order_id" ]; public function order() { return $this->belongsTo(Order::class); }
Para hacer pruebas:
php artisan tinker
Creamos una orden:
$order = App\Models\Order->factory->create();
Creamos un pago asociado a la orden anterior:
$payment = App\Models\Order::factory->create(["order_id" => $order->id]);
Está estblaceida una relación, de tal manera que podemos acceder al pago desde la orden:
$order->payment;
Y desde el pago a la orden a la que pertenece:
$payment->order;
Si hubiéramos puesto los paréntesis en payment u order, obtendríamos un query builder que nos permitiría afinar más la consulta encadenando métodos ($payment->order()->where(...))
Relaciones uno a muchos
Por ejemplo, un usuario puede tener múltiples órdenes. Sin embargo, una orden pertenece a un único usuario.
Para estas relaciones se necesitan claves foráneas.
El modelo que pertenece a otro es el que lleva la clave foránea. Para el ejemplo, anterior, una orden pertenece al usuario, así que será la orden quien lleve la clave foránea del usuario.
Vamos la migración donde se crea una orden:
// code class createOrdersTable extends Migration { public function up() { Schema::create("orders", function (Blueprint $table) { $table->id(); $table->string("status")->default("pending"); // Clave foránea: $table->bigInteger("customer_id")->unsigned(); $table->timestamps(); // Establecemos la relación de la clave foránea con la clave // primaria de la tabla padre $table->foreign("customer_id")->references("id")->on("users"); }); } // code }
Ahora tenemos que relacionar a nivel de modelo las órdenes y los pagos. Vamos al model Order.php:
namespace App; use App\Model\User; use Illuminate\Database\Eloquent\Model; class Order extends Model { protected $fillable = [ "status", "customer_id" ]; // code public function user() { // Una orden tiene un pago return $this->belongsTo(User::class); } }
Laravel, cuando indicamos relaciones (por ejemplo, belongsTo), supone que la clave foránea tendrá el nombre del modelo + _id. Por tanto, en el código anterior, Laravel espera que exista en la tabla orders un campo llamado user_id. Si queremos indicar explícitamente el nombre de la clave foránea a utilizar:
public function user() { // Una orden tiene un pago return $this->belongsTo(User::class, "customer_id"); }
Ahora tenemos que ir al modelo User.php para poder decir que puede tener muchas instancias de orden:
namespace App; use App\Models\Order; // code protected $fillable = [ "amount", "payed_at", "order_id" ]; // Un usuario puede tener muchas órdenes public function orders() { return $this->hasMany(Order::class); }
Si queremos indicar explícitamente el nombre de la clave foránea a utilizar:
// Un usuario puede tener muchas órdenes public function orders() { return $this->hasMany(Order::class, "customer_id"); }
Para hacer pruebas:
php artisan tinker
Creamos una instancia de usuario:
$user= App\Models\Order->factory->create();
Creamos una orden asociada al usuario anterior
$order = App\Models\Order::factory->create(["customer_id" => $user->id]);
Está establecida una relación, de tal manera que podemos acceder a la lista de órdenes del usuario:
$user->orders;
Y desde la orden a la que pertenece:
$payment->order;
Relaciones muchos a muchos
Con el ejemplo de la tienda, hay dos relaciones de muchos a muchos:
- Entre producto y orden: un producto puede pertenecer a varias órdenes; una orden puede tener varios productos.
- Entre producto y carrito: un carrito puede tener múltiples productos; un producto puede estar en múltiples carritos al mismo tiempo
Para construir este tipo de relaciones es necesario construir una tabla intermedia. Con las migraciones, Laravel nos facilita esta tarea:
php artisan make:migration CreateCartProductTable
El nombre que usemos en la migración hará que Laravel cree una tabla cart_product
Para la otra relación:
php artisan make:migration CreateOrderProductTable
Los ficheros con las migraciones los veremos en database/migrations/
El siguiente paso es completar las migraciones para poner las claves foráneas:
// code class CreateCartProductTable extends Migration { public function up() { Schema::create("cart_product", function (Blueprint $table) { $table->bigInteger("cart_id")->unsigned(); $table->bigInteger("product_id")->unsigned(); $table->integer("quantity")->unsigned(); $table->foreign("cart_id")->references("id")->on("carts"); $table->foreign("product_id")->references("id")->on("products"); }); } }
En cuanto a la migración de la relación órdenes y productos:
// code class CreateOrderProductTable extends Migration { public function up() { Schema::create("order_product", function (Blueprint $table) { $table->bigInteger("order_id")->unsigned(); $table->bigInteger("product_id")->unsigned(); $table->integer("quantity")->unsigned(); $table->foreign("order_id")->references("id")->on("orders"); $table->foreign("product_id")->references("id")->on("products"); }); } }
Ahora tenemos que modificar los modelos. App\Models\Cart.php:
namespace App\Models; use App\Models\Product; use Illuminate\Database\Eloquent\Model; class Cart extends Model { // Un carrito puede tener múltiples productos public function products() { // Además de indicar la relación, le pedimos que // traiga cierto atributo. Si no lo hiciésemos, Laravel // solo nos traería las claves foráneas return $this->belongsToMany(Product::class)->withPivot("quantity"); } }
Para el modelo para las órdenes, App\Models\Order.php:
// code class Order extends Model { // Una orden puede tener múltiples productos public function products() { // Además de indicar la relación, le pedimos que // traiga cierto atributo. Si no lo hiciésemos, Laravel // solo nos traería las claves foráneas return $this->belongsToMany(Product::class)->withPivot("quantity"); } }
Para terminar, el modelo Producto:
// code class Product extends Model { protected $fillable = [ "title", "description", "price", "stock", "status", ]; public function carts() { return $this->belongsToMany(Cart::class)->withPivot("quantity"); } public function orders() { return $this->belongsToMany(Order::class)->withPivot("quantity"); } }
Ya estaríamos listos para ejecutar las migraciones:
php artisan migrate:fresh --seed
Podemos ahora hacer pruebas con:
php artisan tinker
$user = App\Models\User::class->factory()->create(); $order = App\Models\Order::class->factory()->create(["customer_id" => $user->id]); $cart = App\Models\Cart::class->factory()->create(); $product = App\Models\Product::first();
Ahora para relacionar:
$order->products()->attach([1 => ["quantity" => 1], 2 => ["quantity" => 10]]); $order = $order->fresh();
Acceder a los productos:
$order->products;
Relaciones a través de relaciones
Si miramos nuestro modelo de datos, pagos, órdenes y usuarios están relacionados. A través de la orden, podemos ver que un usuario tiene varios pagos. Esto solo será posible si el pago tiene una clave foránea de la instancia que usaremos como intermediario. En este caso es “order_at”.
Modelo de usuario (app/Models/User.php):
// code public function payments() { // Indicamos que queremos llegar al pago a través de la orden // Entre orden y usuario hay una clave foránea 'customer_id' return $this->hasManyThrough(Payment::class, Order::class, "customer_id") }
Esta función nos permite que desde una instanacia de User podamos acceder a su lista de pagos.
Lo podemos ver en funcionamiento:
php artisan tinker
$user = App\Models\User::first(); $order = $user->orders()->save(App\Models\Order::class->factory())->make()); $user->orders; $payment = $order->payment()->save(App\Models\Payment::class->factory()->make()); $user->payments;
Relaciones polimórficas uno a uno
Una imagen puede pertenecer a un producto o a un usuario.
En la migración de las imágenes:
// code public function up() { Schema::create("images", function (Blueprint $table) { $table->id(); $table->string("path"); $table->timestamps(); // Nuevo: $table->morphs("imageable"); }); }
$table->morphs() se encargará de crear los campos imageable_id e imageable_type.
Iremos a los modelos involucrados, Image y User.
// code class User extends Authenticatable { // code public function image() { return $this->morphOne(Image::class, "imageable"); } }
// code class Image extends Authenticatable { // code // El nombre del método lo ponemos como el del parámetro que // le pasamos a 'morphOne' en el modelo de usuario y en // la migración. public function imageable() { return $this->morphTo(); } }
Relaciones polimórficas uno a muchos
Relación entre Product e Image. Un producto puede tener múltiples imágenes.
Con la migración vista en la sección anterior de la tabla de imágenes (donde añadimos $table->morphs('imageable'), solo tenemos que ir al modelo de Product.
// code public function images() { return $this->morphMany(Image::class, "imageable"); }
Relaciones polimórficas muchos a muchos
Migración (se simplifican las migraciones de Producto y Orden):
class CreateProductablesTable extends Migration { public function up() { Schema::create("productables", function (Blueprint $table) { $table->bigInteger("product_id")->unsigned(); $table->integer("quantity")->unsigned(); $table->integer("productable"); $table->foreign("product_id")->references("id")->on("products"); }); } }
Ahora vamos al modelo de producto porque puede pertenecer a varios carritos y a varias órdenes:
// code public function carts() { return $this->morphedByMany(Cart::class, "productable")->withPivot("quantity"); } // code public function orders() { return $this->morphedByMany(Order::class, "productable")->withPivot("quantity"); }
Ahora vamos al modelo de la orden:
// code public function products() { return $this->morphToMany(Product::class, "productable")->withPivot("quantity"); }
En el modelo de Cart:
// code public function products() { return $this->morphToMany(Product::class, "productable")->withPivot("quantity"); }
