Jump to content

Organising hierarchical data in an array (using foreach?)


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.

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • 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.