Modelos

icon picker
Polimorfismo ?????????

La relación 1:1 o 1:n

Las principales relaciones que ofrece Eloquent:
hasMany y pertenecen a Muchos .
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 ( pertenece a ).

La relación n:n


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

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 : pertenece a 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 pertenecen a muchos pero de varias tablas.
morphedByMany : este es el pertenecen a muchos 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 ( 'actores' , función ( Blueprint $tabla ) {
$tabla- > id () ;
$tabla- > cadena ( 'nombre' ) -> único () ;
$tabla- > cadena ( 'slug' ) -> único () ;
$tabla -> marcas de tiempo () ;
}) ;
Solo proporcionamos un nombre y un slug, en cuanto a las categorías.
Regeneramos las tablas:
php artesanal migrar:fresco
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' ] ;
películas de función pública ()
{
return $ this- > morphToMany ( Film :: class , 'filmable' ) ;
}

Actor

Será lo mismo para el modelo Actor :
películas de función pública ()
{
return $ this- > morphToMany ( Film :: class , 'filmable' ) ;
}

Película

Para el modelo Film , se proporcionan estas dos relaciones:
categorías de función pública ()
{
return $ this- > morphedByMany ( Categoría :: clase , 'filmable' ) ;
}
actores de la función pública ()
{
return $ this- > morphedByMany ( Actor :: class , 'filmable' ) ;
}

La población

Llenaremos las tablas para nuestras pruebas.
Necesitaremos una fábrica para los actores:
php artesanal hacer: fábrica ActorFactory
image.png
Con este código:
< ?php
espacio de nombres Base de datos\Fábricas;
use App\Models\Actor;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Soporte\Str;
clase ActorFactory extiende Factory
{
protegido $modelo = Actor :: clase ;
definición de función pública ()
{
$nombre = $esto -> farsante -> nombre () ;
volver [
'nombre' = > $nombre,
'slug' = > Str::slug ( $nombre ) ,
] ;
}
}
Todo lo que queda es modificar el código de DatabaseSeeder :
< ?php
espacio de nombres Base de datos\Sembradores;
use Illuminate\Database\Seeder;
use App\Models\ { Película, Categoría, Actor } ;
use Illuminate\Soporte\Str;
clase DatabaseSeeder extiende Seeder
{
/**
* Sembrar la base de datos de la aplicación.
*
* @retorno nulo
*/
ejecución de función pública ()
{
Actor::factory () -> contar ( 10)->crear () ;
$categorias = [
'Comedia' ,
'drama' ,
'Compartir' ,
'Fantástico' ,
'Terror' ,
'animado' ,
'Espionaje' ,
'Guerra' ,
'Policía' ,
'Pornográfico' ,
] ;
foreach ( $categorías como $categoría ) {
Categoría::create ([ 'nombre' = > $categoría, 'slug' = > Str::slug ( $categoría )]) ;
}
$ids = rango ( 1 , 10 ) ;
Película::fábrica () -> contar ( 40)->crear () -> cada ( función ( $película ) uso ( $ids ) {
barajar ( $ids ) ;
$ película- > categorías () -> adjuntar ( array_slice ( $ ids, 0 , rand ( 1,4 ) )) ;
barajar ( $ids ) ;
$ película- > actores () -> adjuntar ( array_slice ( $ ids, 0 , rand ( 1,4 ) )) ;
}) ;
}
}
Y corremos la población:
php artesanal db: semilla
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 ):
arranque de función pública ()
{
Ver::compositor ([ 'índice' , 'crear' , 'editar' ] , función ( $ver ) {
$ver- > with ( 'categorías' , Categoría::todas ()) ;
}) ;
}
Añadiremos los actores:
use App\Models\ { Categoría, Actor } ;
...
$ver- > with ( 'actores' , Actor::all ()) ;

La carretera

También necesitamos agregar una ruta para la selección de películas por actor:
Ruta::controlador ( MovieController::clase)->grupo ( función () {
...
Ruta::get ( 'actor/{slug}/películas' , 'índice' ) -> nombre ( 'películas.actor' ) ;
}) ;
image.png

Controlador

En el controlador por el momento tenemos este código:
índice de función pública ( $ slug = null )
{
$consulta = $slug ? Categoría::whereSlug ( $slug ) -> firstOrFail () -> películas () : Película::consulta () ;
$películas = $ consulta- > withTrashed () -> más antigua ( 'título' ) -> paginar ( 5 ) ;
vista de retorno ( 'índice' , compacto ( 'películas' , '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\ { Película, Categoría, Actor } ;
use Illuminate\Support\Fachadas\Route;
...
índice de función pública ( $ slug = null )
{
$modelo = nulo ;
si ( $slug ) {
if ( Ruta::nombreRutaActual () == 'películas.categoría' ) {
$modelo = nuevaCategoría ;
} más {
$modelo = nuevo actor;
}
}
$consulta = $modelo ? $modelo -> whereSlug ( $slug ) -> firstOrFail () -> películas () : Película::consulta () ;
$películas = $ consulta- > withTrashed () -> más antigua ( 'título' ) -> paginar ( 5 ) ;
vista de retorno ( 'índice' , compacto ( 'películas' , 'slug' )) ;
}

La vista de índice

Ahora todo lo que tenemos que hacer es actualizar la vista de índice :
< div clase = "seleccionar" >
< select onchange = "ventana.ubicación.href=este.valor" >
< option value = "{{ route('movies.index') }}" @unless($slug) seleccionado @endunless > Todos los actores </ option >
@foreach($actores como $actor)
< option value="{{ route('movies.actor', $actor- > slug) }}" {{ $slug == $actor->slug ? 'seleccionado': '' }}>{{ $actor->nombre }} </ opción >
@endforeach
</ seleccionar >
</ div >
Agregue la lista junto a la lista de categorías:
image.png
¡Y todo funciona! Pero es la elección por categoría o por actor. No podemos combinar los dos con nuestro código, pero estaremos satisfechos con él...

Ver una película

En la hoja de una película todavía no tenemos los actores, así que los vamos a agregar. Si recuerda, hicimos un enlace implícito para que el cargador de categorías se cargara cuando pasamos el identificador de una película en la url ( RouteServiceProvider ):
arranque de función pública ()
{
...
Route::bind ( 'película' , función ( $valor ) {
return Movie::with ( 'categorías' ) -> buscar ( $value ) ?? abortar ( 404 ) ;
}) ;
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.