Из-за того, как работает загруженная загрузка, нет ничего, что можно действительно сделать для запуска SQL, чтобы выполнить то, что вы ищете.
Когда вы делаете Product::with('parent')->get()
, он выполняет два запроса.
Во-первых, он выполняет запрос, чтобы получить все продукты:
select * from `products`
Далее он выполняет запрос, чтобы получить нетерпеливые нагруженные родителей:
select * from `products` where `products`.`id` in (?, ?, ?)
Количество параметров (?
) соответствует количеству результатов первого запроса. Как только второй набор моделей был восстановлен, функция match()
используется для связывания объектов друг с другом.
Чтобы сделать то, что вы хотите, вам нужно будет создать новое отношение и переопределить метод match()
. Это будет обрабатывать нетерпеливый аспект загрузки. Кроме того, вам необходимо переопределить метод для обработки ленивого аспекта загрузки.
Во-первых, создать пользовательский класс отношений:
class CustomBelongsTo extends BelongsTo
{
// Override the addConstraints method for the lazy loaded relationship.
// If the foreign key of the model is 0, change the foreign key to the
// model's own key, so it will load itself as the related model.
/**
* Set the base constraints on the relation query.
*
* @return void
*/
public function addConstraints()
{
if (static::$constraints) {
// For belongs to relationships, which are essentially the inverse of has one
// or has many relationships, we need to actually query on the primary key
// of the related models matching on the foreign key that's on a parent.
$table = $this->related->getTable();
$key = $this->parent->{$this->foreignKey} == 0 ? $this->otherKey : $this->foreignKey;
$this->query->where($table.'.'.$this->otherKey, '=', $this->parent->{$key});
}
}
// Override the match method for the eager loaded relationship.
// Most of this is copied from the original method. The custom
// logic is in the elseif.
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
$foreign = $this->foreignKey;
$other = $this->otherKey;
// First we will get to build a dictionary of the child models by their primary
// key of the relationship, then we can easily match the children back onto
// the parents using that dictionary and the primary key of the children.
$dictionary = [];
foreach ($results as $result) {
$dictionary[$result->getAttribute($other)] = $result;
}
// Once we have the dictionary constructed, we can loop through all the parents
// and match back onto their children using these keys of the dictionary and
// the primary key of the children to map them onto the correct instances.
foreach ($models as $model) {
if (isset($dictionary[$model->$foreign])) {
$model->setRelation($relation, $dictionary[$model->$foreign]);
}
// If the foreign key is 0, set the relation to a copy of the model
elseif($model->$foreign == 0) {
// Make a copy of the model.
// You don't want recursion in your relationships.
$copy = clone $model;
// Empty out any existing relationships on the copy to avoid
// any accidental recursion there.
$copy->setRelations([]);
// Set the relation on the model to the copy of itself.
$model->setRelation($relation, $copy);
}
}
return $models;
}
}
После того как вы создали свой собственный класс отношений, вам необходимо обновить модель, чтобы использовать этот обычай отношения. Создайте новый метод на вашей модели, который будет использовать ваши новые отношения CustomBelongsTo
, и обновите свой метод отношений parent()
, чтобы использовать этот новый метод, а не базовый метод belongsTo()
.
class Product extends Model
{
// Update the parent() relationship to use the custom belongsto relationship
public function parent()
{
return $this->customBelongsTo('App\Product', 'parent_id', 'id');
}
// Add the method to create the CustomBelongsTo relationship. This is
// basically a copy of the base belongsTo method, but it returns
// a new CustomBelongsTo relationship instead of the original BelongsTo relationship
public function customBelongsTo($related, $foreignKey = null, $otherKey = null, $relation = null)
{
// If no relation name was given, we will use this debug backtrace to extract
// the calling method's name and use that as the relationship name as most
// of the time this will be what we desire to use for the relationships.
if (is_null($relation)) {
list($current, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$relation = $caller['function'];
}
// If no foreign key was supplied, we can use a backtrace to guess the proper
// foreign key name by using the name of the relationship function, which
// when combined with an "_id" should conventionally match the columns.
if (is_null($foreignKey)) {
$foreignKey = Str::snake($relation).'_id';
}
$instance = new $related;
// Once we have the foreign key names, we'll just create a new Eloquent query
// for the related models and returns the relationship instance which will
// actually be responsible for retrieving and hydrating every relations.
$query = $instance->newQuery();
$otherKey = $otherKey ?: $instance->getKeyName();
return new CustomBelongsTo($query, $this, $foreignKey, $otherKey, $relation);
}
}
Справедливое предупреждение, все это не было протестировано.
Почему бы не поместить собственный идентификатор продукта в поле, если он не является родителем?? –
Если бы я начинал с нуля, я бы подумал, что сделаю это, но, к сожалению, я создаю службу API Laravel для огромной старой базы данных/базы данных. Если бы я изменил работу этого поля, это сломало бы код в другом месте. Я подумал о добавлении нового поля, которое это делает, но мне нужно добавить тонну устаревшего кода в каждом месте, где продукт может быть введен в устаревший сайт - настоящий кошмарный проект. –
Я надеялся каким-то образом добиться этого, используя Laravel. Если каждая альтернатива окажется невозможной, я буду пинговать вас, и вы ответите так, чтобы хотя бы ваш ответ помог другим сделать то же самое, хотя это мне не помогло. Я собираюсь дать этот вопрос немного больше времени, прежде чем сдаться, хотя. –