Русское сообщество разработки на PHP-фреймворке Laravel.
Ты не вошёл. Вход тут.
Страницы 1
Здравствуйте.
У меня не как не получается отобразить категории с 3-4 уровнем вложенности, только с 2 уровнем вложенности выходит.
@foreach($categories->where('parent_id', 0) as $category)
<div>{{ $category->name }}</div>
@foreach($categories->where('parent_id', $category->id) as $child)
<div>{{ $child->name }}</div>
@endforeach
@endforeach
Вот этим кодом отрисовывается только 2 уровня, как можно рекурсивно категории отрисовать что б 3-4 уровня были? Как вы это делаете?
Не в сети
Рекурсивно (вызов самого себя) это не значит итеративно (через цикл). Для этого делается отдельный шаблон (функция, …) и он вызывает сам себя.
@include('categories', [ 'cat' => $categories->where('parent_id', 0) ])
@foreach ($cat as $category) <div>...</div> @include('categories', ['cat' => ...where...]) @endforeach
Но вообще делать запросы к БД в шаблонах это плохая практика.
Не в сети
Но вообще делать запросы к БД в шаблонах это плохая практика
я когда увидел where в примерах, подумал про коллекции. на коллекциях же тоже есть where.
Не в сети
Спасибо за подробный ответ!
Это не запросы, это на уровне коллекций все идет) даже если это были бы запросы, то возвращался бы билдер так как нет "get" или "pluck".
я когда увидел where в примерах, подумал про коллекции. на коллекциях же тоже есть where.
Да это коллекция, вы правильно подумали.
Просто во всех туториалах и ответах, делают 2 запроса к бд (создают связь на саму себя) на поле parent_id. А с коллекциями получается всего один запрос.
Не в сети
Сегодня заметил что при подключении шаблона, создаются запросы к бд.
// controller
$categories = Category::parent()-with('children')->orderBy('parent_id, 'asc')->get();
// Mutator
public function scopeParent($query)
{
return $query-where('parent_id',0);
}
// Relation
public function children()
{
return $this->hasMany(Category::class, 'parent_id');
}
В шаблоне
<div class="category-tree">
@include('partials.tree', ['categories' => $categories])
</div>
// Partials.tree
<ul>
@foreach($categories as $category)
<li>{{ $category->name }}</li>
@if($category->children)
@include('partials.tree', ['categories' => $category->children])
@endif
@endforeach
</ul>
Запросы создает код @include('partials.tree', ['categories' => $category->children]). Но почему? Коллекция ведь уже загружена. Может дебагбар ошибается?)
Не в сети
не ошибается. связь children загружена только для верхнего уровня категорий, для вложенных обращение к ней всё равно происходит – модель не может знать что их там нет. самый простой вариант – загружать с ->with(['children', 'children.children']) – там всё равно будет «холостой» запрос на несуществующий уровень, но зато в момент исполнения шаблона модели уже будут знать что вложенных в них элементов не существует.
Не в сети
constb, спасибо помогло!
Но это всеравно не то… Аж 4 запроса к базе идет при 3х уровневой иерархии. Блин, столько разработчиков на ларе, и неужели никто ничего подобного не делал?
Нашел работающий код, но он отдает один сгенерированный массив да еще и не удобно, по ссылке.
// Then write a function and call it recursively as many times as you need. filter method would be really helpful to work with your categories collection
// Something like this should work:
function getCategories($categories, &$result, $parent_id = 0, $depth = 0)
{
//filter only categories under current "parent"
$cats = $categories->filter(function ($item) use ($parent_id) {
return $item->parent_id == $parent_id;
});
//loop through them
foreach ($cats as $cat)
{
//add category. Don't forget the dashes in front. Use ID as index
$result[$cat->id] = str_repeat('-', $depth) . $cat->title;
//go deeper - let's look for "children" of current category
getCategories($categories, $result, $cat->id, $depth + 1);
}
}
//get categories data. In this case it's eloquent.
$categories = Category::get();
//if you don't have the eloquent model you can use DB query builder:
//$categories = DB::table('categories')->select('id', 'parent_id', 'title')->get();
//prepare an empty array for $id => $formattedVal storing
$result = [];
//start by root categories
getCategories($categories, $result);
Вообще решение с collection->filter() самое подходящее. На первом проходе он отдает коллекцию родителей, потом потомков и тд. Осталось только как нибудь массив сформировать примерно такой
array(
0 => array(
'parent' => Category() // model
'children' => collection() // collection of children models
)
1 => array(
'parent' => Category() // model
'children' => collection() // collection of children models
)
2 => array(
'parent' => Category() // model
'children' => collection() // collection of children models
)
)
Не в сети
4 запроса это не так много, но мне кажется тут оптимизировать проще и быстрее через кэш, чем пытаться ещё лучше обрабатывать эти категории. дело в том что список категорий вряд ли меняется часто, даже если время жизни кэша поставить на 5 минут – это всё равно экономия будет в несколько запросов на 5 минут – овчинка выделки мягко говоря не стоит. я бы оставил 4 запроса и завернул их в cache()->remember(). из кэша, даже если он на файлах или в базе, выбирать данные всё равно намного быстрее, не говоря уже о мемкэше или редисе…
сам кэш удобно сбрасывать на событиях модели кстати – буквально в несколько строчек кода выходит и потом работает само
Не в сети
Доброго всем здравия. В ларавель я пока только вникаю, но всё-же вставлю свои 5 коп. по теме.
Есть одно, удобное на мой взгляд, решение для древовидных структур, в т.ч. категорий, комментариев и т.п.
По моему опыту, категорий обычно не запредельно много. Поэтому выбираю всегда все категории одним запросом, а вот уже полученный массив рекурсивно строю в дерево с учётом значения parent_id (зависимостей).
Способ этот в своё время нашёл вроде-бы на "тостере" https://toster.ru/q/7550#answer_32413
Как это грамотно реализовать в рамках laravel - самому интересно.
Изменено Real (04.06.2017 00:52:51)
Всё, что существует на свете, когда-то было мечтой.
Не в сети
Страницы 1