Jump to content

Organising hierarchical data in an array (using foreach?)


rpmorrow

Recommended Posts

Hi,

 

I have a DB table with the following columns

 

[id]  [parent_id]

  1          0

  6          0

  7          9

  8          9

  9          10

10          0

 

I want to read from the DB and output it like a hierarchical structure. I keep trying with arrays and foreach loops but am failing. There is just some logic I'm overlooking I think.

 

The output should look like this

1

6

10

    9

        7

 

Can anyone help me please?

It is for creating a menu (each row represents an item in the menu).

 

"parent_id" contains a value representing the "id" of the menu level above that row.

 

Anything with a "parent_id" of 0 is a top level item.

id=9 is a sublevel of id=10

id=7 and id=8 are sublevels of id=9

 

I missed a digit from the ouptut in my first post  :-[ It should be;

 

The output should look like this

1

6

10

    9

        7

        8

Use the composite pattern to achieve this:

 

$root = new MenuComposite();
while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
    $mc = new MenuLeaf($row);
    $root->addItem($mc);
}
print $root->render();

abstract class MenuComponent {
    private $_data;
    
    public function __construct(array $data) {
        $this->_data = new ArrayObject($data, ArrayObject::ARRAY_AS_PROPS);
    }
    
    public function getData() {
        return $this->_data;
    }
    
    public function getId() { return $this->getData()->id; }
    public function getParentId() { return $this->getData()->parent_id; }
    
    public function addItem(MenuComponent $mc) {}
    
    public function render() { return ''; }
}

class MenuComposite extends MenuComponent {
    private $_items;
    
    
    public function getItems() {
        if (null === $this->_items) {
            $this->_items = new ArrayObject();
        }
        return $this->_items;
    }
    
    public function addItem(MenuComponent $mc) {
        if (0 !== $mc->getParentId()) { // composite
            $this->getItems()->offsetSet($mc->getId(), $mc);
        } else if ($this->getItems()->offsetExists($mc->getParentId())) { // leaf
            $p = $this->getItems()->offsetGet($mc->getParentId());
            if ($p instanceof MenuComposite) {
                $p->addItem($mc);
            } else { // convert MenuLeaf to MenuComposite
                $p = new MenuComposite($p->getData());
                $p->addItem($mc);
                $this->getItems()->offsetSet($mc->getParentId(), $p);
            }
        } else {
            throw new Exception('Parent item with id ' . $mc->getParentId() . ' does not exist.');
        }
    }
    
    public function render() {
        $list = '<ul>';
        foreach ($this->_items as $id => $mc) {
            $list .= '<li>' . $mc->render() . '</li>';
        }
        $list .= '<ul>';
        return $list;
    }
}

class MenuLeaf extends MenuComponent {
    public function render() {
        return '<a href="' . $this->getData()->href . '">' . $this->getData()->label . '</a>';
    }
}

Here's an improved version:

 

$rows = array();
while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
    $pid = intval($row['parent_id']);
    $rows[$pid][] = $row;
}

$root = new Menu();
foreach ($rows as $pid => $array) {
    foreach ($array as $row) {
        $id = intval($row['id']);
    
        if (!isset($rows[$id])) { $root->add(new MenuLeaf($row)); }
        else { $root->add(new MenuComposite($row)); }
    }
}
print $root->render();

abstract class MenuComponent {
    private $data = array();
    
    public function __construct($data = array()) {
        $this->data = $data;
    }
    
    abstract public function render();
}

class MenuComposite extends MenuComponent {
    private $components = array();
    
    public function add($key, MenuComponent $item) {
        $this->components[$key] = $item;
    }
    
    public function render() {
        if (!sizeof($this->components)) return '';
        
        $html = '<a href="#">' . $this->data['label'] . '</a><ul>';
        foreach ($this->components as $component) {
            $html .= '<li>' . $component->render() . '</li>';
        }
        return $html . '</ul></a>';
    }
}

class Menu extends MenuComposite {
    public function render() {
        if (!sizeof($this->components)) return '';
        
        $html = '<ul>';
        foreach ($this->components as $component) {
            $html .= '<li>' . $component->render() . '</li>';
        }
        return $html . '</ul>';
    }
}

class MenuLeaf extends MenuComponent {
    public function render() {
        $data = $this->getData(); 
        return '<a href="#">' . $data['label'] . '</a>';
    }
}

Thanks!

 

I'm pretty new to the OO stuff, but that looks good.

 

Unfortunatley I'm getting a parse error....

 

Parse error: syntax error, unexpected T_CLASS

 

... refering to the line starting with

abstract class MenuComponent {

 

I can't see any missing parentheses or semicolons. Any ideas why I'd get the error?

 

EDIT: In fact, if I delete all code before that point, I still get the error.

Well these only serve the purpose of an example and I am not going to correct any error because you shouldn't be using them in production environments they are not meant to. An alternate solution production ready may be Zend_Navigation. Easy to use http://framework.zend.com/manual/en/zend.navigation.html

Written code only serves as an example and may not fit your current project requirements. The example just gives you a basic idea of what may accomplish your goal. My code has a lot missing and it's up to you to fill in the blanks and properly test it before using it in a production environment.

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.