Search code examples
phpooptreefluent-interface

PHP OOP : Fluent interface and tree graphs


I'm trying to create a fluent interface for tree objects.

Here's a simplified example of what I currently do :

<?php
class node {
    private $childs = array();
    private $parent;

    public function __construct($parent = null) {
        $this->parent = $parent;
    }

    public function addChild($child) {
        $this->childs[] = $child;
        return $this;
    }

    public function createChild() {
        return $this->addChild(new node($this));
    }

    public function setFoo() {
        /* do something */
        return $this;
    }
}

$root = new node();

$root   ->addChild((new node($root))
            ->setFoo()
        )->addChild((new node($root))
            ->setFoo()
        );
?>

I would like to reduce the part where I create the tree. What I want to do is something like this :

$root->createChild()->setFoo();
$root->createChild()->setFoo();

in one line. And without having to explicitly create new nodes instances (like I did in the first code with new operators).

My goal is to be able to create any tree of any order, and its nodes of any degree without having to put a semi-colon in the code.


Solution

  • Rather than adding a createChild function I think you should change your constructor and addChild functions to consistently establish the parent / child relationship in the data. Once you've done that the addChild function and the constructor can be used to do what you described without a createChild function. Right now your constructor allow cross-linking between different trees and branches in the trees so it's something that will probably need to change anyway.

    class node {
        private $childs = array();
        private $parent;
    
        public function __construct(node $parent = null) {
            if(!is_null($parent)) {
                $parent->addChild($this);
            }
        }
    
        public function addChild(node $child) {
            $this->childs[] = $child;
            $child->parent = $this;
            return $this;
        }
    
        public function setFoo() {
            /* do something */
            return $this;
        }
    }
    

    With this you can chain new objects into a tree:

    $tree = (new node())->addChild(new node())
                        ->addChild((new node())->setFoo())
                        ->addChild((new node())->addChild(new node())
                                               ->addChild(new node())
                                               ->setFoo()
                        );
    

    Trying to use a createChild function is a catch-22 situation where sometimes you need the parent and sometimes you need the child. You can solve it using a return object that contains both but I think it's a situation that is better avoided. If you don't like the "(new node())" syntax, a static function might be the way to go:

    public static function create(node $parent = null) {
        return new node($parent);
    }
    

    Which might be a little prettier depending on your tastes:

    $tree = node::create()->addChild(node::create())
                          ->addChild(node::create()->setFoo())
                          ->addChild(node::create()->addChild(new node())
                                                   ->addChild(new node())
                                                   ->setFoo()
                          );