Laravel 4 — Красноречивый. Бесконечные дети в полезный массив?

У меня есть таблица категорий. У каждой категории может быть дополнительный родитель (по умолчанию 0, если родителя нет).

Что я хочу сделать, так это построить простое дерево списка html с уровнями категорий.

Примерная дата:

Foods
-- Fruit
---- Apple
---- Banana
---- Orange
-- Veg
---- Cucumber
---- Lettuce
Drinks
-- Alcoholic
---- Beer
---- Vodka
Misc
-- Household Objects
---- Kitchen
------ Electrical
-------- Cooking
---------- Stove
---------- Toaster
---------- Microwave

Обратите внимание, что это должно работать примерно на 10 «уровнях». Я бы хотел, чтобы он был бесконечным, но я действительно не хочу идти по пути использования модели вложенного набора, поскольку это вызовет огромные задержки в этом проекте.

Документы по этому для laravel ужасны, без реальной ссылки на то, с чего даже начать. Я играл с ним в течение нескольких дней, пытаясь понять, что делать, и, кажется, ничего не получается без огромного грязного блока для каждого цикла внутри друг друга 10 раз.

У меня есть дерево данных, использующее в моей модели следующее:

<?php
class Item extends Eloquent {
    public function parent()
    {
        return $this->hasOne('Item', 'id', 'parent_id');
    }

    public function children()
    {
        return $this->hasMany('Item', 'parent_id', 'id');
    }

    public function tree()
    {
        return static::with(implode('.', array_fill(0,10, 'children')))->where('parent_id', '=', '0')->get();
    }
}

Это поднимает всех родительских и дочерних элементов до уровня 10. Это прекрасно работает, но вы не можете ничего сделать с дочерними данными, не имея вручную 10 циклов foreach друг в друге.

Что я здесь делаю неправильно? Конечно, это не должно быть так сложно/плохо выполнено? Все, что я хочу сделать, это получить простой список html с элементами в древовидной структуре.

Я собрал быстрый пример SQLFiddle с фиктивными данными, использованными выше: http://sqlfiddle.com/#!2/e6d18/1


person Sk446    schedule 14.05.2013    source источник
comment
Вложенные наборы на самом деле проще. Кроме того, уже есть некоторые существующие пакеты, такие как вложенные наборы Cartalyst или Баум.   -  person Jason Lewis    schedule 15.05.2013
comment
Джейсон, в чем преимущество вложенных наборов по сравнению с принятым ответом? Я должен выбрать между ними двумя. И с Баумом, кажется, нет никакого способа получить это дерево, если вы также не используете принятый ответ.   -  person kJamesy    schedule 25.10.2013


Ответы (3)


Это было намного веселее, чем мой обычный утренний кроссворд. :)

Вот класс ItemsHelper, который будет делать то, что вы ищете, и, что еще лучше, будет рекурсивно выполняться так далеко, как вы хотите.

app/models/ItemsHelper.php:

<?php

  class ItemsHelper {

    private $items;

    public function __construct($items) {
      $this->items = $items;
    }

    public function htmlList() {
      return $this->htmlFromArray($this->itemArray());
    }

    private function itemArray() {
      $result = array();
      foreach($this->items as $item) {
        if ($item->parent_id == 0) {
          $result[$item->name] = $this->itemWithChildren($item);
        }
      }
      return $result;
    }

    private function childrenOf($item) {
      $result = array();
      foreach($this->items as $i) {
        if ($i->parent_id == $item->id) {
          $result[] = $i;
        }
      }
      return $result;
    }

    private function itemWithChildren($item) {
      $result = array();
      $children = $this->childrenOf($item);
      foreach ($children as $child) {
        $result[$child->name] = $this->itemWithChildren($child);
      }
      return $result;
    }

    private function htmlFromArray($array) {
      $html = '';
      foreach($array as $k=>$v) {
        $html .= "<ul>";
        $html .= "<li>".$k."</li>";
        if(count($v) > 0) {
          $html .= $this->htmlFromArray($v);
        }
        $html .= "</ul>";
      }
      return $html;
    }
  }

Я только что использовал новую установку Laravel 4 и базовое представление hello.php.

Вот мой маршрут в app/routes.php:

Route::get('/', function()
{
  $items = Item::all();
  $itemsHelper = new ItemsHelper($items);
  return View::make('hello',compact('items','itemsHelper'));
});

Хотя в моем представлении не используется переменная items, я передаю ее здесь, потому что вы, вероятно, захотите сделать с ними что-то еще.

И, наконец, в моем app/views/hello.php всего одна строка:

<?= $itemsHelper->htmlList(); ?>

Вывод выглядит следующим образом:

  • Еда
    • Фрукты
      • яблоко
      • Банан
      • апельсин
    • Вег
      • Огурец
      • Латук
  • Напитки
    • Алкоголик
      • Пиво
      • Водка
  • Разное
    • Предметы домашнего обихода
      • Кухня
        • Электрический
          • приготовление еды
            • Печь
            • Тостер
            • микроволновка

Примечание. у вашего скрипта SQL в качестве parent_id для огурца и салата было 5 ("оранжевый"), мне пришлось изменить его на 6 ("овощ").

person Mark Smith    schedule 14.05.2013
comment
@MarkSmith - отличный ответ, спасибо! Просто пытаетесь понять, как бы вы добавили дополнительные значения в массив? Или даже добавить именованные значения? Например: { id: '2', name: 'Parent 2', children: [ { id: '5', name: 'Sub 2a' }, { id: '6', name: 'Sub 2b' } ] } - person DavidP; 22.12.2013
comment
Бесконечно полезно! СПАСИБО! - person jeanfrg; 17.07.2014
comment
Я также хотел бы знать, как добавить дополнительные значения... @MarkSmith - person dcolumbus; 02.09.2014
comment
Это хорошее решение, но есть только одна вещь, которая не является хорошей. Проблема в том, что представление создается в модели, а не в самом представлении, и если вы следуете шаблону MVC, у вас не должно быть никакого HTML нигде, кроме файлов представления. Существует другое решение с рекурсивными вызовами подшаблонов блейда, но все же не очень хорошее, хотя оно следует шаблону MVC. - person ArmeniaH; 03.10.2014
comment
Милая! Есть ли способ вернуть весь объект, который повторяется? Вместо имени? - person Albin N; 15.01.2015
comment
См. мое решение ниже, которое расширяет принятый ответ, но обрабатывает дополнительные данные по запросу. - person Amo; 19.01.2015

Я расширил принятый ответ Марка Смита, чтобы сгенерированные списки могли ссылаться на дополнительные данные, которые передаются в класс.

Класс работает почти так же, но я упаковал его так, чтобы его можно было легко использовать.

Просто укажите вспомогательный класс в вашем контроллере:

use App\Helpers\CategoryHierarchy;

Затем вы можете либо создать экземпляр класса вручную, либо с помощью внедрения метода Laravel 5 все станет еще лучше:

$products = $product->getAllProducts();
$hierarchy->setupItems($products);
return $hierarchy->render();

Это может вывести следующее:

<ul class='simple-list'>
   <li><input type="checkbox" name="hierarchy-checkboxes[]" value="1" >Home delivery</li>
      <ul>
        <li><input type="checkbox" name="hierarchy-checkboxes[]" value="2" >Italian</li>
          <ul>
            <li><input type="checkbox" name="hierarchy-checkboxes[]" value="3" >Pizza</li>
            <li><input type="checkbox" name="hierarchy-checkboxes[]" value="4" >Pasta</li>
          </ul>
        <li><input type="checkbox" name="hierarchy-checkboxes[]" value="5" >Burgers</li>
      </ul>
</ul>

Доступен репозиторий: https://github.com/amochohan/CategoryHierarchy, в котором более подробно объясняется .

person Amo    schedule 19.01.2015

Я использую эти функции, чтобы заставить его работать.

 //Returns Root elements
 public function scopeRoot($query) {
        $all = $query->whereParent(0)->get();
        $collection = $all->filter(function($single) {
            if ($single->ModelFilter('GET')) {
                return $single;
            }
        });
        return $collection;
    }
    //Recursive call
    public function traverse() {
        self::_traverse($this->Children, $array, $this);
        return $array;
    }

    //This function build a multidimensional array based on a collection of elements
    private static function _traverse($collection, &$array, $object) {
        $new_array = array();
        foreach ($collection as $element) {
            self::_traverse($element->Children, $new_array, $element);
        }
        $array[] = $object;
        if (count($new_array) > 0) {
            $array[] = $new_array;
        }
    }

Сначала я получаю коллекцию корневых элементов, которые я передаю в свои представления, где я хочу перечислить дерево...

Тогда я делаю...

 <ul class="bg-info cat-list">
        @foreach($categories as $category)
        <?php
        $array = $category->traverse();
        list_view($array);
        ?>
        @endforeach
    </ul>

Использование этой функции...

//Prints a multidimensional array as a nested HTML list
function list_view($element, $ul = true) {
    foreach ($element as $value) {
        if (!is_array($value)) {
            echo "<li>";
            echo $value->name;
        } else {
            echo ($ul) ? "<ul>" : "<ol>";
            list_view($valuce, $ul);
            echo "</li>";
            echo ($ul) ? "</ul>" : "</ol>";
        }
    }
}

Вывод

Надеюсь, поможет

person Carlos Arturo Alaniz    schedule 24.07.2014