Laravel 4 – Elocuente. ¿Niños infinitos en una matriz utilizable?

Tengo una tabla de categorías. Cada categoría puede tener un padre opcional (por defecto es 0 si no hay padre).

Lo que quiero hacer es construir un árbol de lista html simple con los niveles de las categorías.

Fecha de ejemplo:

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

Tenga en cuenta que esto debe funcionar alrededor de 10 ‘niveles’. Me encantaría que fuera infinito, pero realmente no quiero ir por la ruta del uso de un modelo de conjunto nested, ya que causará grandes retrasos en este proyecto.

Los documentos sobre esto para laravel son terribles, sin una referencia real de por dónde empezar. He estado jugando con él durante días tratando de averiguar qué hacer y parece que no llegaré a ninguna parte sin un gran bloque desordenado de 10 vueltas dentro de cada uno.

Tengo mi árbol de datos usando lo siguiente en mi modelo:

 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(); } } 

Esto pone a todos los padres e hijos hasta un nivel de 10. Esto funciona bien, pero realmente no se puede hacer nada con los datos secundarios sin tener manualmente 10 bucles foreach entre sí.

¿Qué estoy haciendo mal aquí? Seguramente esto no debería ser tan difícil / mal ejecutado? Todo lo que quiero hacer es obtener una lista html simple con los elementos en una estructura de árbol.

He creado un ejemplo rápido de SQLFiddle de los datos ficticios utilizados anteriormente: http://sqlfiddle.com/#!2/e6d18/1

Esto fue mucho más divertido que mi crucigtwig de mañana habitual. 🙂

Aquí hay una clase ItemsHelper que hará lo que está buscando y, aún mejor, se repetirá lo más posible.

app/models/ItemsHelper.php:

 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 .= "
    "; $html .= "
  • ".$k."
  • "; if(count($v) > 0) { $html .= $this->htmlFromArray($v); } $html .= "
"; } return $html; } }

Acabo de utilizar una nueva instalación de Laravel 4 y la vista básica hello.php .

Aquí está mi ruta en la app/routes.php :

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

Aunque mi vista no usa la variable de elementos, la paso aquí porque probablemente también quieras hacer algo más con ellos.

Y finalmente, mi app/views/hello.php solo tiene una línea:

htmlList(); ?>

La salida se ve así:

  • Alimentos
  • Fruta
  • manzana
  • Plátano
  • naranja
  • Veg
  • Pepino
  • Lechuga
  • Bebidas
  • Alcohólico
  • Cerveza
  • Vodka
  • Misc
  • Objetos domésticos
  • Cocina
  • Eléctrico
  • Cocina
  • Estufa
  • Tostadora
  • Microonda

Nota: su SQL Fiddle tenía 5 (“Naranja”) como parent_id para Pepino y Lechuga, tuve que cambiarlo a 6 (“Veg”).

Uso estas funciones para que funcione.

  //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; } } 

Primero obtengo una colección de los elementos raíz que son los que paso a mis vistas donde quiero enumerar el árbol …

Entonces lo hago …

  
    @foreach($categories as $category) traverse(); list_view($array); ?> @endforeach

Usando esta función …

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

    Salida

    Espero eso ayude

    He ampliado la respuesta aceptada por Mark Smith para permitir que las listas generadas hagan referencia a los datos de adición que se pasan a la clase.

    La clase funciona de la misma manera, pero la he empaquetado, así que con suerte se puede usar fácilmente.

    Simplemente haga referencia a la clase de ayuda en su controlador:

     use App\Helpers\CategoryHierarchy; 

    A continuación, puede crear una instancia de la clase de forma manual, o mediante el método de inyección de Laravel 5, las cosas se mejoran aún más:

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

    Esto puede dar como resultado lo siguiente:

     
    • Home delivery
    • Italian
    • Pizza
    • Pasta
  • Burgers
  • Un informe está disponible: https://github.com/drawmyattention/CategoryHierarchy, que explica con más detalle.