Cómo usar relaciones polimórficas en Laravel
Clase 9 de 33 • Curso Avanzado de Laravel
Contenido del curso
Entorno de trabajo y repaso de Laravel
Manejo de tu base de datos con Laravel
La terminal de Laravel
Eventos y tareas de Laravel
Manejo de errores
El corazón de Laravel
Creación de paquetes
- 26

Cómo crear paquetes Laravel con Composer
08:51 min - 27
Propiedades para manejo de dependencias
02:02 min - 28
Comprende el archivo composer.json
02:23 min - 29

Cómo Composer carga clases automáticamente
04:18 min - 30

Crear mis propios Services Providers
08:58 min - 31

Cómo publicar archivos con Service Provider
04:12 min - 32

Instalando paquetes desde GitHub con Composer
10:35 min - 33

Publicar tu paquete PHP en Packages
03:12 min
Con Eloquent puedes construir un sistema de calificaciones flexible y escalable sin duplicar lógica. Aquí verás cómo las relaciones polimórficas permiten que una sola tabla pivot conecte múltiples modelos, cómo estructurar la migración, el modelo Rating y dos traits reutilizables: CanRate y CanBeRated. El resultado: un único flujo para puntuar productos, páginas o categorías con pruebas que pasan.
¿Qué resuelven las relaciones polimórficas en Eloquent?
Las relaciones polimórficas permiten que un modelo pertenezca a más de un tipo de modelo. Con un ejemplo de rating, un usuario o invitado puede puntuar un producto, una página o una categoría con una sola relación. Así evitas múltiples tablas intermedias y repeticiones.
¿Cómo funciona el concepto de morph en Eloquent?
- Un par de columnas define la relación: <nombre>_id y <nombre>_type.
- Con morphs('ratable') y morphs('qualifier') identificas quién es calificado y quién califica.
- Se usa una única tabla pivot para todos los tipos relacionados.
¿Qué roles intervienen en el rating?
- Ratable: la entidad que recibe la calificación, por ejemplo, un producto.
- Qualifier: la entidad que califica, por ejemplo, un usuario.
- Score: el valor de la calificación almacenado en la tabla pivot.
¿Cómo se implementa el sistema de rating con morphs?
Primero se crea la migración para la tabla intermedia y luego el modelo Rating que extiende Pivot. Esto habilita timestamps, score y los tipos polimórficos.
¿Cómo crear la migración de ratings con morphs?
- Ejecuta el comando: php artisan make:migration para crear la tabla.
- Define id, timestamps, score y los morphs para las dos entidades.
Schema::create('ratings', function (Blueprint $table) { $table->id(); $table->morphs('ratable'); // ratable_id, ratable_type $table->morphs('qualifier'); // qualifier_id, qualifier_type $table->unsignedTinyInteger('score'); $table->timestamps(); });
¿Cómo definir el modelo Rating y morphTo?
- Extiende de Pivot en lugar de Model porque es una tabla intermedia.
- Habilita incrementing y referencia la tabla ratings.
- Agrega dos relaciones morphTo: ratable() y qualifier().
use Illuminate\Database\Eloquent\Relations\Pivot; class Rating extends Pivot { public $incrementing = true; protected $table = 'ratings'; public function ratable() { return $this->morphTo(); } public function qualifier() { return $this->morphTo(); } }
¿Qué comandos de artisan intervienen?
- php artisan migrate: crea la tabla ratings.
- php artisan make:model Rating: genera el modelo para la tabla intermedia.
¿Cómo usar traits para calificar y ser calificado?
Para mantener el diseño flexible, se usan traits: uno para quienes califican (CanRate) y otro para quienes son calificados (CanBeRated). Allí se centraliza la relación morphToMany, alias, timestamps, columnas pivot y filtros por tipo.
¿Cómo implementar el trait CanRate con morphToMany?
- La relación ratings($model = null) arma dinámicamente la clase objetivo con get_class o getMorphClass.
- Usa morphToMany con: clase relacionada, nombre de la relación, tabla ratings, qualifier_id y ratable_id.
- Define alias con as('rating'), agrega withTimestamps() y columnas pivot: score y ratable_type.
- Filtra por ratable_type y qualifier_type para asegurar consistencia.
- Expone métodos rate($model, $score) y hasRated($model) para operar y validar no calificar dos veces.
trait CanRate { public function ratings($model = null) { $modelClass = $model ? get_class($model) : $this->getMorphClass(); return $this->morphToMany( $modelClass, 'qualifiers', 'ratings', 'qualifier_id', 'ratable_id' )->as('rating') ->withTimestamps() ->withPivot('score', 'ratable_type') ->wherePivot('ratable_type', $modelClass) ->wherePivot('qualifier_type', $this->getMorphClass()); } public function hasRated($model) { return $this->ratings($model)->whereKey($model->getKey())->exists(); } public function rate($model, $score) { if ($this->hasRated($model)) { return false; } $this->ratings($model)->attach($model->getKey(), [ 'score' => $score, 'ratable_type' => get_class($model), ]); return true; } }
¿Cómo implementar el trait CanBeRated y calcular promedio?
- La relación se llama qualifiers() e invierte las llaves pivot.
- Permite calcular el promedio con avg('score'). Si no hay datos, devuelve 0.
trait CanBeRated { public function qualifiers($model = null) { $modelClass = $model ? get_class($model) : $this->getMorphClass(); return $this->morphToMany( $modelClass, 'ratables', 'ratings', 'ratable_id', 'qualifier_id' )->withPivot('score') ->wherePivot('ratable_type', $this->getMorphClass()); } public function averageRating($modelClass = null) { return (float) ($this->qualifiers($modelClass)->avg('score') ?? 0); } }
¿Cómo integrarlo en modelos y pruebas?
- En User usa el trait CanRate para que el usuario pueda calificar.
- En Product usa CanBeRated para que el producto sea calificado.
- En pruebas, si aparece “espera un objeto y le pasamos un string”, pasa el modelo completo y guarda ratable_type con get_class.
- Ejecuta tests y valida que el attach incluya score y type.
Palabras clave y habilidades trabajadas:
- Relaciones polimórficas: morphs, morphTo, morphToMany.
- Tabla pivot: ratings, score, ratable_type, qualifier_type, timestamps.
- Traits reutilizables: CanRate, CanBeRated con alias as('rating').
- Prácticas de calidad: evitar calificar dos veces con hasRated y cubrir con tests.
¿Te gustaría que también los usuarios se califiquen entre sí? Comenta cómo lo implementarías y qué validaciones agregarías.