polimorfismo - relaciones polimorficas laravel
Laravel-Ansioso cargando modelos relacionados con relaciones polimórficas (5)
Como mencionó João Guilherme, esto se solucionó en la versión 5.3. Sin embargo, me he encontrado con el mismo error en una aplicación donde no es posible actualizar. Así que he creado un método de anulación que aplicará la corrección a las API heredadas. (Gracias João, por señalarme en la dirección correcta para producir esto).
Primero, crea tu clase Override:
namespace App/Overrides/Eloquent;
use Illuminate/Database/Eloquent/Relations/MorphTo as BaseMorphTo;
/**
* Class MorphTo
* @package App/Overrides/Eloquent
*/
class MorphTo extends BaseMorphTo
{
/**
* Laravel < 5.2 polymorphic relationships fail to adopt anything from the relationship except the table. Meaning if
* the related model specifies a different database connection, or timestamp or deleted_at Constant definitions,
* they get ignored and the query fails. This was fixed as of Laravel v5.3. This override applies that fix.
*
* Derived from https://github.com/laravel/framework/pull/13741/files and
* https://github.com/laravel/framework/pull/13737/files. And modified to cope with the absence of certain 5.3
* helper functions.
*
* {@inheritdoc}
*/
protected function getResultsByType($type)
{
$model = $this->createModelByType($type);
$whereBindings = /Illuminate/Support/Arr::get($this->getQuery()->getQuery()->getRawBindings(), ''where'', []);
return $model->newQuery()->withoutGlobalScopes($this->getQuery()->removedScopes())
->mergeWheres($this->getQuery()->getQuery()->wheres, $whereBindings)
->with($this->getQuery()->getEagerLoads())
->whereIn($model->getTable().''.''.$model->getKeyName(), $this->gatherKeysByType($type))->get();
}
}
A continuación, necesitará algo que le permita a sus clases de modelos hablar realmente con su encarnación de MorphTo en lugar de las de Eloquent. Esto se puede hacer ya sea mediante un rasgo aplicado a cada modelo, o un hijo de Illuminate / Database / Eloquent / Model que se extiende por sus clases de modelo en lugar de Illuminate / Database / Eloquent / Model directamente. Elegí convertir esto en un rasgo. Pero en caso de que decidas convertirlo en una clase infantil, lo dejo en la parte en la que infiere el nombre como un aviso que es algo que deberías considerar:
<?php
namespace App/Overrides/Eloquent/Traits;
use Illuminate/Support/Str;
use App/Overrides/Eloquent/MorphTo;
/**
* Intended for use inside classes that extend Illuminate/Database/Eloquent/Model
*
* Class MorphPatch
* @package App/Overrides/Eloquent/Traits
*/
trait MorphPatch
{
/**
* The purpose of this override is just to call on the override for the MorphTo class, which contains a Laravel 5.3
* fix. Functionally, this is otherwise identical to the original method.
*
* {@inheritdoc}
*/
public function morphTo($name = null, $type = null, $id = null)
{
//parent::morphTo similarly infers the name, but with a now-erroneous assumption of where in the stack to look.
//So in case this App''s version results in calling it, make sure we''re explicit about the name here.
if (is_null($name)) {
$caller = last(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2));
$name = Str::snake($caller[''function'']);
}
//If the app using this trait is already at Laravel 5.3 or higher, this override is not necessary.
if (version_compare(app()::VERSION, ''5.3'', ''>='')) {
return parent::morphTo($name, $type, $id);
}
list($type, $id) = $this->getMorphs($name, $type, $id);
if (empty($class = $this->$type)) {
return new MorphTo($this->newQuery(), $this, $id, null, $type, $name);
}
$instance = new $this->getActualClassNameForMorph($class);
return new MorphTo($instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name);
}
}
Puedo cargar relaciones / modelos polimórficos sin ningún problema de n + 1. Sin embargo, si intento acceder a un modelo relacionado con el modelo polimórfico, aparece el problema n + 1 y parece que no puedo encontrar una solución. Aquí está la configuración exacta para verlo localmente:
1) Nombre de la tabla DB / datos
history
companies
products
services
2) Modelos
// History
class History extends Eloquent {
protected $table = ''history'';
public function historable(){
return $this->morphTo();
}
}
// Company
class Company extends Eloquent {
protected $table = ''companies'';
// each company has many products
public function products() {
return $this->hasMany(''Product'');
}
// each company has many services
public function services() {
return $this->hasMany(''Service'');
}
}
// Product
class Product extends Eloquent {
// each product belongs to a company
public function company() {
return $this->belongsTo(''Company'');
}
public function history() {
return $this->morphMany(''History'', ''historable'');
}
}
// Service
class Service extends Eloquent {
// each service belongs to a company
public function company() {
return $this->belongsTo(''Company'');
}
public function history() {
return $this->morphMany(''History'', ''historable'');
}
}
3) Enrutamiento
Route::get(''/history'', function(){
$histories = History::with(''historable'')->get();
return View::make(''historyTemplate'', compact(''histories''));
});
4) La plantilla con n + 1 registrado solo debido a $ history-> historable-> company-> name, comenta, n + 1 desaparece ... pero necesitamos el nombre de la compañía relacionada distante:
@foreach($histories as $history)
<p>
<u>{{ $history->historable->company->name }}</u>
{{ $history->historable->name }}: {{ $history->historable->status }}
</p>
@endforeach
{{ dd(DB::getQueryLog()); }}
Necesito poder cargar los nombres de la empresa con entusiasmo (en una sola consulta) ya que es un modelo relacionado de los modelos de relación polimórfica Product
y Service
. He estado trabajando en esto durante días pero no puedo encontrar una solución. History::with(''historable.company'')->get()
simplemente ignora a la company
en historable.company
. ¿Cuál sería una solución eficiente a este problema?
No estoy 100% seguro de esto, porque es difícil volver a crear su código en mi sistema, pero quizás belongTo(''Company'')
debería estar morphedByMany(''Company'')
. También morphToMany
probar morphToMany
. Pude obtener una relación polimórfica compleja para cargar correctamente sin múltiples llamadas. ?
Puedes separar la colección, y luego cargar perezosos cada uno:
$histories = History::with(''historable'')->get();
$productCollection = new Illuminate/Database/Eloquent/Collection();
$serviceCollection = new Illuminate/Database/Eloquent/Collection();
foreach($histories as $history){
if($history->historable instanceof Product)
$productCollection->add($history->historable);
if($history->historable instanceof Service)
$serviceCollection->add($history->historable);
}
$productCollection->load(''company'');
$serviceCollection->load(''company'');
// then merge the two collection if you like
foreach ($serviceCollection as $service) {
$productCollection->push($service);
}
$results = $productCollection;
Probablemente no sea la mejor solución, agregando protected $with = [''company''];
Como lo sugiere @damiani es una buena solución, pero depende de la lógica de su negocio.
Solución:
Es posible, si añades:
protected $with = [''company''];
Tanto a los modelos de Service
como a los de Product
. De esa manera, la relación de la company
se carga con entusiasmo cada vez que se carga un Service
o un Product
, incluso cuando se carga a través de la relación polimórfica con la History
.
Explicación:
Esto dará como resultado 2 consultas adicionales, una para el Service
y otra para el Product
, es decir, una consulta para cada tipo de tipo de historable_type
. Por lo tanto, su número total de consultas, independientemente del número de resultados n
va desde m+1
(sin cargar la relación de company
distante) a (m*2)+1
, donde m
es el número de modelos vinculados por su polimorfo relación.
Opcional:
La desventaja de este enfoque es que siempre cargará con entusiasmo la relación de la company
en los modelos de Service
y Product
. Esto puede o no ser un problema, dependiendo de la naturaleza de sus datos. Si esto es un problema, puede usar este truco para cargar automáticamente la company
solo cuando se llama la relación polimórfica.
Agrega esto a tu modelo de History
:
public function getHistorableTypeAttribute($value)
{
if (is_null($value)) return ($value);
return ($value.''WithCompany'');
}
Ahora, cuando cargue la relación polimórfica historable
, Eloquent buscará las clases ServiceWithCompany
y ProductWithCompany
, en lugar de Service
o Product
. Luego, crea esas clases y configúralas dentro de ellas:
ProductWithCompany.php
class ProductWithCompany extends Product {
protected $table = ''products'';
protected $with = [''company''];
}
ServiceWithCompany.php
class ServiceWithCompany extends Service {
protected $table = ''services'';
protected $with = [''company''];
}
... y finalmente, puedes eliminar protected $with = [''company''];
de las clases base de Service
y Product
.
Un poco hacky, pero debería funcionar.