我正在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);
}
}
您将有不同类型的菜单项,如示例中使用的Link
和Misc
。
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'));
您还可以启用链接,以避免使用变量和各种其他好东西,如工厂等。