确定PHP中函数调用的顺序


Determining order of function calls in PHP

我正在PHP 7中编写一个名为MenuBuilder的类,该类用于在web应用程序中构建导航菜单。

类的工作方式是建立一个输出缓冲区,用于保存菜单的标记。

它首先在class MenuBuilder:中声明一个空变量

private $output_buffer = '';

有一种方法可以添加到其中:

protected function add_to_output_buffer($input = '') {
    if ($input !== '') {
        $this->output_buffer .= $input;
    }
}

以及一种显示菜单标记的方法:

public function render() {
    echo $this->output_buffer;
}

到目前为止相当简单。问题是。。。当我向菜单添加链接时,我使用以下方法:

public function add_menu_item($item) {
    $this->menu_items[] = $item;
}

这建立了一个数组($this->menu_items(,然后还有另一个方法循环其中的项,将它们添加到输出缓冲区。它很长,但它的缩写版本在这里:

public function create_links() {
       foreach ($this->menu_items as $item) {
           $output = '';
           $output = '<a href="'.$item['link'].'">'.$item['text'].'</a>';
           $this->add_to_output_buffer($output);
       }
}

create_links()中有很多逻辑,因为各种类和其他东西都被应用到链接上,但本质上它必须这样工作,因为它需要遍历整个链接数组。

问题是:如果我想在菜单中的任何一点添加随机HTML,我有一个功能:

public function add_misc_html($html = '') {
    $this->output_buffer .= $html;
}

但在我使用这些函数的脚本中,它不知道调用的顺序。一个例子:

$MenuBuilder = new MenuBuilder;
$MenuBuilder->add_menu_item(['text' => 'Link 1', 'link' => '#']);
$MenuBuilder->add_menu_item(['text' => 'Link 2', 'link' => '#']);
$MenuBuilder->add_misc_html('<h1>testing add_misc_html</h1>');
$MenuBuilder->add_menu_item(['text' => 'Link 3', 'link' => '#']);
$MenuBuilder->create_links();
$MenuBuilder->render();

所以问题是想要的输出是:

<a href="#">Link 1</a>
<a href="#">Link 2</a>
<h1>testing add_misc_html</h1>
<a href="#">Link 3</a>

但这不起作用,因为当我调用create_links()时,它不知道add_misc_html,也不知道它在调用MenuBuilder 的整体结构中的调用位置

我可以通过什么方式实现期望的输出?请注意,在render()之前,add_misc_html()需要在任何被调用的地方工作。类中有大量其他东西可以打开和关闭菜单,因此它需要能够在任何时候修改$this->output_buffer

这是因为add_misc_html直接写入输出缓冲区,其中add_menu_item修改对象的结构,所以不能混合这些调用。您必须先将修改后的对象状态写入缓冲区,然后再将"misc"HTML写入缓冲区。不过这很糟糕。

你可以想出很多解决方案,但我建议你不要有输出缓冲区,并可能用其他几个类来扩展类的逻辑。

class MenuBuilder {
    protected $items = [];
    public function add(MenuItem $item) {
        $this->items[] = $item;
    }
    public function render() {
        return join('', $this->items);
    } 
}

您将有不同类型的菜单项,如示例中使用的LinkMisc

interface MenuItem {
    public function __toString();
}
class LinkMenuItem implements MenuItem {
    protected $link;
    protected $text;
    public function __construct($link, $text) {
        $this->link = $link;
        $this->text = $text;
    }
    public function __toString() {
        return '<a href="' . $this->link . '">'. $this->text .'</a>';
    }
}
class MiscMenuItem implements MenuItem {
    protected $html;
    public function __construct($html) {
        $this->html = $html;
    }
    public function __toString() {
        return $this->html;
    }
}

现在你的课将被这样使用。

$builder = new MenuBuilder();
$builder->add(new LinkMenuItem('#', 'Link 1'));
$builder->add(new LinkMenuItem('#', 'Link 2'));
$builder->add(new MiscMenuItem('<h1>Testing html</h1>');
$builder->add(new LinkMenuItem('#', 'Link 3'));

以及何时要渲染。

$builder->render();

编辑

根据评论中的讨论,我开始认为您在将对象分层呈现为HTML时遇到了问题。同样,有很多方法可以做到这一点,我只是提出了一个

你可以添加一个基类,你的其他菜单项将扩展

abstract class ParentMenuItem extends MenuItem {
    protected $children = [];
    public function addChild(MenuItem $child) {
        $this->children[] = $child;
    }
    public function __toString() {
        return '<ul>' . join(null, $this->children) . '</ul>';
    }
}

然后你的LinkMenuItem会像这个

class LinkMenuItem extends ParentMenuItem {
    protected $link;
    protected $text;
    public function __construct($link, $text) {
        $this->link = $link;
        $this->text = $text;
    }
    public function __toString() {
        return '<a href="' . $this->link . '">'. $this->text .'</a>' . parent::__toString();
    }
}

当你想添加子项时,你会将它们添加到它们所属的菜单项中(duh?(

$builder = new MenuBuilder();
$link1 = new LinkMenuItem('#', 'Link 1');
$link1->addChild(new LinkMenuItem('#', 'Child1'));
$link1->addChild(new LinkMenuItem('#', 'Child2'));
$builder->add($link1);
$builder->add(new LinkMenuItem('#', 'Link 2'));
$builder->add(new MiscMenuItem('<h1>Testing html</h1>'));
$builder->add(new LinkMenuItem('#', 'Link 3'));

您还可以启用链接,以避免使用变量和各种其他好东西,如工厂等。