plantillas - laravel foreach index
Laravel: devolviendo el propietario del espacio de nombre de una relación polimórfica (5)
Al usar Laravel 5.2 (o posterior), puede usar la nueva función morphMap
para resolver este problema. Solo agregue esto a la función de boot
en la app/Providers/AppServiceProvider
:
Relation::morphMap([
''post'' => /App/Models/Post::class,
''video'' => /App/Models/Video::class,
]);
Más sobre eso: nicolaswidart.com/blog/laravel-52-morph-map
Puedo encontrar una serie de discusiones con respecto a esto, pero no hay una solución clara. Aquí hay dos enlaces, aunque voy a cubrir todo en mi propia pregunta aquí.
Explicación simple
Esta es una explicación simple de mi problema para cualquiera que ya esté familiarizado con las relaciones polimórficas de Laravel.
Al usar $ morphClass, los contenidos de $ morphClass que se guardan en la base de datos como el morph "tipo" se usan para el nombre de clase cuando se intenta encontrar el propietario de una relación polimórfica. Esto da como resultado un error ya que todo el punto de $ morphClass es que no es un nombre de la clase con nombre de espacio completo.
¿Cómo se define el nombre de clase que debe usar la relación polimórfica?
Explicación más detallada
Esta es una explicación más detallada que explica exactamente lo que estoy tratando de hacer y por qué estoy tratando de hacerlo con ejemplos.
Cuando se utilizan relaciones polimórficas en Laravel, se guarda todo lo que se guarda como el tipo "morph_type" en la base de datos.
Entonces en este ejemplo:
class Photo extends Eloquent {
public function imageable()
{
return $this->morphTo();
}
}
class Staff extends Eloquent {
public function photos()
{
return $this->morphOne(''Photo'', ''imageable'');
}
}
class Order extends Eloquent {
public function photos()
{
return $this->morphOne(''Photo'', ''imageable'');
}
}
La base de datos se vería así:
staff
- id - integer
- name - string
orders
- id - integer
- price - integer
photos
- id - integer
- path - string
- imageable_id - integer
- imageable_type - string
Ahora la primera fila de fotos puede verse así:
id, ruta, imageable_id, imageable_type
1, image.png, 1, Staff
Ahora puedo acceder a la Foto desde un modelo de Staff o desde un miembro del Staff desde un modelo de Foto.
//Find a staff member and dump their photos
$staff = Staff::find(1);
var_dump($staff->photos);
//Find a photo and dump the related staff member
$photo = Photo::find(1);
var_dump($photo->imageable);
Hasta aquí todo bien. Sin embargo, cuando los espacio de nombres me encuentro con un problema.
namespace App/Store;
class Order {}
namespace App/Users;
class Staff {}
namespace App/Photos;
class Photo {}
Ahora lo que se guarda en mi base de datos es esto:
id, ruta, imageable_id, imageable_type
1, image.png, 1, Aplicación / Usuarios / Personal
Pero no quiero eso. ¡Es una idea terrible tener nombres de clases con espacios de nombres completos guardados en la base de datos de esa manera!
Afortunadamente, Laravel tiene una opción para establecer una variable $ morphClass. Al igual que:
class Staff extends Eloquent {
protected $morphClass = ''staff'';
public function photos()
{
return $this->morphOne(''Photo'', ''imageable'');
}
}
Ahora la fila en mi base de datos se verá así, ¡lo cual es increíble!
id, ruta, imageable_id, imageable_type
1, image.png, 1, personal
Y obtener las fotos de un miembro del personal funciona absolutamente bien.
//Find a staff member and dump their photos
$staff = Staff::find(1);
//This works!
var_dump($staff->photos);
Sin embargo, la magia polimórfica de encontrar al propietario de una foto no funciona:
//Find a photo and dump the related staff member
$photo = Photo::find(1);
//This doesn''t work!
var_dump($photo->imageable);
//Error: Class ''staff'' not found
Es de suponer que debe haber una forma de informar la relación polimórfica de qué nombre de clase usar al usar $ morphClass, pero no puedo encontrar ninguna referencia sobre cómo debería funcionar esto en los documentos, en el código fuente o a través de Google.
¿Alguna ayuda?
Deje que laravel lo ponga en el db - namespace y todo. Si necesita el nombre de clase corto para algo además de hacer que su base de datos sea más bonita, defina un descriptor de acceso para algo como:
<?php namespace App/Users;
class Staff extends Eloquent {
// you may or may not want this attribute added to all model instances
// protected $appends = [''morph_object''];
public function photos()
{
return $this->morphOne(''App/Photos/Photo'', ''imageable'');
}
public function getMorphObjectAttribute()
{
return (new /ReflectionClass($this))->getShortName();
}
}
El razonamiento al que siempre vuelvo en escenarios como este es que Laravel está bastante probado y funciona como se espera en su mayor parte. ¿Por qué luchar contra el marco si no tiene que hacerlo, especialmente si simplemente está molesto por el espacio de nombres que está en el DB? Estoy de acuerdo en que no es una buena idea hacerlo, pero también siento que puedo pasar el tiempo más útil para superarlo y trabajar en el código de dominio.
Hay dos maneras sencillas, una más abajo, otra en la respuesta de @ lukasgeiter, tal como propuso Taylor Otwell, que definitivamente sugiero que se revise también:
// app/config/app.php or anywhere you like
''aliases'' => [
...
''MorphOrder'' => ''Some/Namespace/Order'',
''MorphStaff'' => ''Maybe/Another/Namespace/Staff'',
...
]
// Staff model
protected $morphClass = ''MorphStaff'';
// Order model
protected $morphClass = ''MorphOrder'';
hecho :
$photo = Photo::find(5);
$photo->imageable_type; // MorphOrder
$photo->imageable; // Some/Namespace/Order
$anotherPhoto = Photo::find(10);
$anotherPhoto->imageable_type; // MorphStaff
$anotherPhoto->imageable; // Maybe/Another/Namespace/Staff
No utilizaría nombres de clase reales ( Order
y Staff
) para evitar posibles duplicados. Hay muy pocas posibilidades de que algo se llame MorphXxxx
por lo que es bastante seguro.
Esto es mejor que almacenar espacios de nombres (no me importa el aspecto de la base de datos, sin embargo, sería un inconveniente en caso de que cambie algo, digamos en lugar de App/Models/User
use Cartalyst/Sentinel/User
etc.) porque todo lo que necesita es para intercambiar la implementación a través de la configuración de alias.
Sin embargo, también hay un inconveniente: no sabrá cuál es el modelo, simplemente verificando el DB en caso de que le importe.
Me gusta la solución @JarekTkaczyks y le sugiero que la use. Pero, en aras de la exhaustividad, hay otra forma en que Taylor menciona brevemente github
Puede agregar un descriptor de acceso de atributo para el atributo imageable_type y luego usar una matriz "classmap" para buscar la clase correcta.
class Photo extends Eloquent {
protected $types = [
''order'' => ''App/Store/Order'',
''staff'' => ''App/Users/Staff''
];
public function imageable()
{
return $this->morphTo();
}
public function getImageableTypeAttribute($type) {
// transform to lower case
$type = strtolower($type);
// to make sure this returns value from the array
return array_get($this->types, $type, $type);
// which is always safe, because new ''class''
// will work just the same as new ''Class''
}
}
Sin embargo, tenga en cuenta que todavía necesitará el atributo morphClass
para realizar consultas desde el otro lado de la relación.
Para cargar ansiosamente las relaciones polimórficas, por ejemplo, Photo::with(''imageable'')->get();
es necesario devolver nulo si el tipo está vacío.
class Photo extends Eloquent {
protected $types = [
''order'' => ''App/Store/Order'',
''staff'' => ''App/Users/Staff''
];
public function imageable()
{
return $this->morphTo();
}
public function getImageableTypeAttribute($type) {
// Illuminate/Database/Eloquent/Model::morphTo checks for null in order
// to handle eager-loading relationships
if(!$type) {
return null;
}
// transform to lower case
$type = strtolower($type);
// to make sure this returns value from the array
return array_get($this->types, $type, $type);
// which is always safe, because new ''class''
// will work just the same as new ''Class''
}
}