我在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
以及在调用{{1}的整体结构中调用它的位置}}
我可以通过哪些方式获得所需的输出?请注意MenuBuilder
需要在add_misc_html()
之前的任何地方调用。类中有很多其他东西可以打开菜单并关闭它,因此它需要能够在任何时候修改render()
。
答案 0 :(得分:3)
这是因为您的add_misc_html
直接写入输出缓冲区,其中add_menu_item
修改了对象的结构,您无法混合这些调用。您必须先将修改后的对象状态写入缓冲区,然后才能写入&#34; misc&#34; 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();
}
}
当你想要添加孩子时,你会将它们添加到他们所属的菜单项(呃?)
$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'));
您还可以启用链接以避免使用变量和各种其他好东西,例如工厂等。