В Laravel у меня есть таблица, которая содержит id, parent_id, slug (самообращение),
Когда у меня есть идентификатор, мне нужно получить всех его предков в таком формате (разделенные знаком «/»).
level1/level2/level3
Но эффективным способом без пакета типа "laravel-nestedset ".
Я реализовал это так.
public function parent()
{
return $this->belongsTo('Collection', 'parent_id');
}
public function getParentsAttribute()
{
$parents = collect([]);
$parent = $this->parent;
while(!is_null($parent)) {
$parents->push($parent);
$parent = $parent->parent;
}
return $parents;
}
Любой другой способ сделать это эффективно и разделенный «/»?
у вас есть столбец с именем уровня в БД с уровнем? если бы вы это сделали, вы могли бы динамически построить запрос, чтобы получить всех родителей, затем использовать что-то вроде GROUP_CONCAT и позволить БД делать всю работу. PS Я не использую Laravel, но я сделал это с вложенными наборами, простые отношения родитель-потомок - это немного сложнее, потому что вам нужно соединение для каждого уровня.
Любой способ сделать это с помощью красноречивого "with ()" - Нетерпеливая загрузка?
@Flame Ты имеешь в виду как return Collection::find(33)->parents->implode('/'); ?
да, я думаю, что это работает, так как ->parents-> будет относиться к вашему получателю атрибутов. Возможно, вы сможете добавить protected $appends = ['parents'] к своей модели, чтобы это свойство автоматически заполнялось.
@Flame Я тоже так думал, но у меня пустой белый экран. :(
@stackminu попробуйте отладить саму родительскую коллекцию, чтобы проверить, пуста ли она, или поставьте точки останова в геттере.
Коллекция @Flame Parent возвращает все поля с «родителем».
о, извините, конечно parent — это объект, вы должны называть его как ->implode('yourproperty', '/'), где yourproperty — свойство родительского объекта. Вызов с одним параметром работает только в том случае, если ваша коллекция содержит только простые скалярные значения.
@Flame Круто, это сработало, но результат изменился. :/
в этой ссылке вы можете увидеть два разных подхода к работе с иерархическими данными в реляционной базе данных mikehillyer.com/articles/managing-hierarchical-data-in-mysql






Если вы знаете, сколько максимум уровней может быть вложено, вы можете использовать Eager Loading. Скажем, если максимальная глубина составляет 3 уровня, вы можете сделать:
$model->with('parent.parent.parent');
Вы также можете использовать рекурсию вместо цикла.
public function getParentsAttribute()
{
if (!$this->parent) {
return collect([]);
}
return collect($this->parent->parents)->push($this->parent);
}
Если вы хотите добавить и первый объект (self), полный вызов будет таким:
$model->parents->push($model)->reverse->implode('attr_name', '/');
Который вы также можете обернуть в атрибут
public function getPathAttribute() {
return $model->parents->push($model)->reverse->implode('attr_name', '/');
}
И ставь лайк $model->path;
Это делает его довольно неудобным, поскольку ваши родители также будут вложены в ваш объект. Вам нужно будет получить к ним доступ, используя $mainObject->parent->parent->parent, поэтому это неудобно, если вам нужен массив родителей.
@Flame, правда, но по крайней мере меньше запросов
@NikolaiKiselev, почему, по вашему мнению, это решение выполняет меньше запросов? для извлечения полных деревьев в модели списка смежности нам нужно присоединиться на основе глубины нашего дерева. (в этом примере 3)
Я не уверен, что это действительно разрешает 1 запрос, даже если это так, увеличение скорости спорно, поскольку вы торгуете циклом PHP для создания массива родителей из этого вложенного свойства, VS оригинальное решение, которое выполняет 3 небольших запроса и помещает их в массив PHP
@hoseinz3, каждый раз, когда $parent->parent вызывается в while цикле getParentsAttribute(), будет запускаться новый запрос к БД, если только эти вложенные отношения не были предварительно загружены ранее
@Flame кажется, ваше решение хорошее, но его можно комбинировать с загрузкой Eager, без вреда :)
@stackminu, вы можете использовать рекурсию, я обновил ответ.
После небольшого разговора в комментариях я думаю, что это хорошее решение:
// YourModel.php
// Add this line of you want the "parents" property to be populated all the time.
protected $appends = ['parents'];
public function getParentsAttribute()
{
$collection = collect([]);
$parent = $this->parent;
while($parent) {
$collection->push($parent);
$parent = $parent->parent;
}
return $collection;
}
Затем вы можете получить своих родителей, используя:
YourModel::find(123)->parents (экземпляр коллекции)YourModel::find(123)->parents->implode('yourprop', '/') (сжато в строку, см. https://laravel.com/docs/5.4/collections#method-implode)YourModel::find(123)->parents->reverse()->implode('yourprop', '/') (обратный порядок https://laravel.com/docs/5.4/collections#метод-обратный)Как заметил Николай Киселев https://stackoverflow.com/a/55103589/1346367, вы также можете комбинировать это с этим, чтобы сэкономить несколько запросов:
protected $with = ['parent.parent.parent'];
// or inline:
YourModel::find(123)->with(['parent.parent.parent']);
Это предварительно загружает родителя при загрузке объекта. Если вы решите не использовать это, родитель (лениво) загружается, как только вы вызываете $yourModel->parent.
Извините, у меня есть еще один вопрос, связанный с этим. Есть ли способ получить annsestor и self ? level1/level2/level3/WITH-THIS.
Я думаю, вы могли бы просто поместить $this в коллекцию $ внутри getParentsAttribute(), вы также можете поместить туда метод reverse().
Я думаю, что это уже хорошее решение. Кроме того, атрибут
parentsвозвращает коллекцию (хотя это может быть и простой массив). Затем просто вызовите$collection->implode('/'), чтобы превратить его в строку