Ultima Laravel
Share
Explore
Modelos

icon picker
Polimorfismo

La relación 1:1 o 1:n

Las principales relaciones que ofrece Eloquent:
hasMany y belongsTo
la relación hasOne es simplemente hasMany limitada a un solo registro y no se usa mucho.
image.png
Nosotros a tiene una b ( hasOne ) o a tiene varias b ( hasMany ).
Lo contrario: b es propiedad de a (belongsTo ).

La relación n:n


image.png
Tenemos a pertenece a uno o más b (belongsToMany)).
Y tenemos que b pertenece a uno o más a (belongsToMany ).

Relación de una tabla a varias tablas

Tipo de relación 1:n

Ahora imagina esta situación:
image.png
La tabla c se puede relacionar con la tabla a o con la tabla b . En esta situación, ¿cómo administrar una clave externa en la tabla c? ¿Cómo llamarlo y cómo saber con qué tabla está en relación?
Podemos ver claramente que necesitaremos otro dato: seguramente conocer la tabla en relación .
Como necesitamos dos piezas de información, necesitamos dos columnas:
image.png
Entonces tenemos dos columnas:
relatable_id : la clave externa que almacena la identificación del registro relacionado
relatable_type : la clase del modelo relacionable.
Aquí está la figura completa con los nombres de estas relaciones:
image.png
morphOne : es el hasOne pero de varias tablas.
morphMany : es el hasMany pero de varias tablas.
morphTo :belongsTo pero destinado a varias tablas.

Tipo de relación n:n

Podemos tener el mismo razonamiento para una relación n:n con varias tablas en un lado de la relación:
image.png
morphToMany : este es el belongsToMany pero de varias tablas.
morphedByMany : este es el belongsToMany pero en la dirección de varias tablas.

Los datos

Vamos a ver un ejemplo de la gestión de películas introduciendo el polimorfismo. Hasta entonces teníamos categorías y películas, vamos a agregar actores, ¡todavía es bastante relevante para las películas! Un actor puede actuar en varias películas y una película tiene varios actores, así que de nuevo tenemos una relación n:n . Obviamente, podríamos usar una segunda tabla dinámica, pero será más elegante con solo una y polimorfismo.

Migraciones

Nada cambia en la migración de categorías y películas. Eliminamos la tabla dinámica que habíamos creado. Y creamos una migración para nuestra nueva tabla dinámica:
php artisan make:migration filmables
image.png
Y vamos a proporcionar este código:
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class Filmables extends Migration
{
public function up()
{
Schema::create('filmables', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->foreignId('film_id')
->constrained()
->onDelete('cascade')
->onUpdate('cascade');
$table->morphs('filmable');
});
}

public function down()
{
Schema::dropIfExists('filmables');
}
}

El método morphs es útil porque crea automáticamente las dos columnas para la relación polimórfica.
También necesitamos una migración y un modelo para los actores:
php ar
image.png
/


Con este código:
Schema::create('actors', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->string('slug')->unique();
$table->timestamps();
});
Solo proporcionamos un nombre y un slug, en cuanto a las categorías.
Regeneramos las tablas:
php artisan migrate:fresh
Si todo va bien, deberíamos tener las 4 tablas que nos interesan:

image.png

Las relaciones

Categoría

Para el modelo Categoría se proporciona esta relación (que reemplaza a la anterior):
protected $fillable = ['name', 'slug'];

public function films()
{
return $this->morphToMany(Film::class, 'filmable');
}

Actor

Será lo mismo para el modelo Actor :
public function films()
{
return $this->morphToMany(Film::class, 'filmable');
}

Film

Para el modelo Film , se proporcionan estas dos relaciones:
public function categories()
{
return $this->morphedByMany(Category::class, 'filmable');
}

public function actors()
{
return $this->morphedByMany(Actor::class, 'filmable');
}

Factory

Llenaremos las tablas para nuestras pruebas.
Necesitaremos una fábrica para los actores:
php artisan make:factory ActorFactory
image.png
Con este código:
<?php

namespace Database\Factories;

use App\Models\Actor;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class ActorFactory extends Factory
{

protected $model = Actor::class;

public function definition()
{
$name = $this->faker->name();
return [
'name' => $name,
'slug' => Str::slug($name),
];
}
}
Todo lo que queda es modificar el código de DatabaseSeeder :
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\{ Film, Category, Actor };
use Illuminate\Support\Str;

class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
Actor::factory()->count(10)->create();

$categories = [
'Comédie',
'Drame',
'Action',
'Fantastique',
'Horreur',
'Animation',
'Espionnage',
'Guerre',
'Policier',
'Pornographique',
];

foreach($categories as $category) {
Category::create(['name' => $category, 'slug' => Str::slug($category)]);
}

$ids = range(1, 10);
Film::factory()->count(40)->create()->each(function ($film) use($ids) {
shuffle($ids);
$film->categories()->attach(array_slice($ids, 0, rand(1, 4)));
shuffle($ids);
$film->actors()->attach(array_slice($ids, 0, rand(1, 4)));
});
}
}

php artisan db:seed
Esta vez he planeado nombres de categorías realistas y no aleatorios:
image.png
También planeé 10 actores con nombres aleatorios:
image.png
También tenemos 40 películas. Y, por último, en la tabla dinámica también creé enlaces aleatorios entre categorías, actores y películas. Aquí hay una descripción general:
image.png
Vemos que para cada registro tenemos el nombre del modelo y la identificación como se esperaba.

La página de bienvenida

En la página de inicio en este momento tenemos esto:
image.png
Una lista de películas con botones de acción, y también una opción por categoría. Todo eso todavía funciona, pero sería bueno agregar también la elección por actor.
Usamos un compositor de vistas para generar el conjunto de categorías para las vistas (en AppServiceProvider ):
public function boot()
{
View::composer(['index', 'create', 'edit'], function ($view) {
$view->with('categories', Category::all());
});
}
Añadiremos los actores:
use App\Models\{ Category, Actor };

...

$view->with('actors', Actor::all());

Ruta

También necesitamos agregar una ruta para la selección de películas por actor:
use App\Models\{ Category, Actor };

...

$view->with('actors', Actor::all());
image.png

Controlador

En el controlador por el momento tenemos este código:
public function index($slug = null)
{
$query = $slug ? Category::whereSlug($slug)->firstOrFail()->films() : Film::query();
$films = $query->withTrashed()->oldest('title')->paginate(5);
return view('index', compact('films', 'slug'));
}
Distinguimos el caso donde tenemos un parámetro, por lo tanto un slug y el caso donde no lo tenemos. Estuvo bien solo con las categorías, pero ya no porque tenemos dos casos con slugs. Así que vamos a cambiarlo así:
use App\Models\{Film, Category, Actor};
use Illuminate\Support\Facades\Route;
...

public function index($slug = null)
{
$model = null;

if($slug) {
if(Route::currentRouteName() == 'films.category') {
$model = new Category;
} else {
$model = new Actor;
}
}

$query = $model ? $model->whereSlug($slug)->firstOrFail()->films() : Film::query();
$films = $query->withTrashed()->oldest('title')->paginate(5);
return view('index', compact('films', 'slug'));
}

La vista índex

Ahora todo lo que tenemos que hacer es actualizar la vista index :
<div class="select">
Share
 
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.