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?

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.