2

I have an object that contains other objects that could conceivably be given the containing object.

$a = new Container();
$b = new Container();
$a->add($b);
$b->add($a);

So to test for this eventuality I have added 2 functions that make sure no closed loops happen.

class Object{
  $contents = array();
  $parents = array();

  function add($content){
    if(is_a($content, "Container")){
      $content->_registerParent($this);
      $this->_checkLoop($content);
      $this->contents[] = $content;
    }
  }

  function _registerParent($parent){
    if(count($this->parents) >0){
      throw new Exception("Closed Reference Loop");
    }
    $this->parents[] = $parent;
  }

  function _checkLoop($child){
    if($child===$this){
      throw new Exception("Closed Reference Loop");
    }
    foreach($this->parents as $parent){
      $parent->_checkLoop($child)
    }
  }
}

This works just fine and it fairly low overhead. I am looking to expand this functionality to other classes and need to know the best way to do it. Should I make all classes that could be added and contain other containers extend a root container object? Extending would work, but I would like the flexibility to apply this to a class that may already extend another class.

Or should I pass the functionality to the classes as traits? Theoretically this sounds like the best option but I don't have much experience with Traits and autoloading them.

I would use implement, but the test and tracking doesn't change from class to class.

Tyson of the Northwest
  • 2,086
  • 2
  • 21
  • 34

1 Answers1

1

As you realize implementing interface is not enough in your case. Extending is lacking of flexibility. So the best way will be using Traits.

Personally, I will go with Traits and Interfaces combined. They are easy to use and works well with extending/implementing other functionalities. Example:

Trait

trait MyTestTrait
{
    public function registerParent($parent){
        if(count($this->parents) >0){
            throw new Exception("Closed Reference Loop");
        }
        $this->parents[] = $parent;
    }

    public function checkLoop($child){
        if($child===$this){
            throw new Exception("Closed Reference Loop");
        }
        foreach($this->parents as $parent){
            $parent->checkLoop($child)
        }
    }
}

Interface

interface MyTestInterface
{
    public function registerParent($parent);
    public function checkLoop($child);
}

Usage in class

class Object extends SomeAbstract implements MyTestInterface, AnotherInterface {
    use MyTestTrait;

    $contents = array();
    $parents = array();

    function add($content){
        if(is_a($content, "Container")){
            $content->registerParent($this);
            $this->checkLoop($content);
            $this->contents[] = $content;
        }
    }
}

Thanks to interface, we are sure that registerParent() and checkLoop() methods are present. Thanks to trait we are implementing them without compromising the ability of class extending. Cheers!

Arius
  • 1,387
  • 1
  • 11
  • 24
  • I did not think of combining implements and traits in this manner. It would allow me to use is_a to test if an object implements an interface to detect if it has the trait or implements an alternative version. – Tyson of the Northwest Aug 15 '14 at 17:07