doudou1438 2016-09-27 11:38
浏览 233
已采纳

在PHP中确定函数调用的顺序

I'm writing a class - called MenuBuilder - in PHP 7 which is intended to build navigation menus in a web application.

The way in which the class works is by building up an output buffer which holds the markup for the menus.

It starts off by declaring an empty variable inside class MenuBuilder:

private $output_buffer = '';

There is a method to add to it:

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

And a method to display the menu markup:

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

So far quite straightforward. Here's the problem... When I add a link to the menu I use this method:

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

This builds up an array ($this->menu_items) and there is then another method which cycles through the items in it to add them to the output buffer. It's quite long but a shortened version of it is here:

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

There is quite a lot of logic in create_links() because various classes and other things get applied to the links, but essentially it has to work like this because it needs to loop through the full array of links.

The problem: If I want to add random HTML at any point in the menu, I have a function to do this:

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

But in my script which uses these functions, it doesn't know the order in which things are being called. An example:

$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();

So the problem is that the desired output is:

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

But this won't work, because when I call create_links() it's not aware of add_misc_html and where this is called in the overall structure of the calls to MenuBuilder

What ways can I achieve the desired output? Please note that add_misc_html() needs to work anywhere it gets called, prior to render(). There is a whole load of other stuff in the class to open the menu and close it, so it needs to be able to modify the $this->output_buffer at any point.

  • 写回答

1条回答 默认 最新

  • dsp1836 2016-09-27 12:05
    关注

    This is because your add_misc_html writes directly to your output buffer, where add_menu_item modifies the structure of your object, you can't mix these calls. You must first write your modified object state to the buffer and only then write "misc" HTML to the buffer. This is pretty bad though.

    There are many solutions you can come up with but the one I will suggest to you is to not have an output buffer at all, and possibly extend the logic of the class with a couple other classes.

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

    Where you will have different types of menu items, like Link and Misc that are used in your example.

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

    And now your class will be used like this.

    $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'));
    

    And when you want to render.

    $builder->render();
    

    Edit

    Based on the discussion in the comments I am coming to think that you are having trouble rendering the objects as HTML hierarchically. Again there are a number of ways you can go about this, I am just proposing one

    You can add a base class that your other menu items will extend

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

    Then your LinkMenuItem will look like this

    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();
        }
    }
    

    When you want to add children you will add them to the menu item they belong to (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'));
    

    You can also enable chaining to avoid using variables and all kinds of other good stuff like factories etc.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 请问为什么我配置IPsec后PC1 ping不通 PC2,抓包出来数据包也并没有被加密
  • ¥200 求博主教我搞定neo4j简易问答系统,有偿
  • ¥15 nginx的使用与作用
  • ¥100 关于#VijeoCitect#的问题,如何解决?(标签-ar|关键词-数据类型)
  • ¥15 一个矿井排水监控系统的plc梯形图,求各程序段都是什么意思
  • ¥50 安卓10如何在没有root权限的情况下设置开机自动启动指定app?
  • ¥15 ats2837 spi2从机的代码
  • ¥200 wsl2 vllm qwen1.5部署问题
  • ¥100 有偿求数字经济对经贸的影响机制的一个数学模型,弄不出来已经快要碎掉了
  • ¥15 数学建模数学建模需要