Composite
----------
This is a useful pattern to learn where you need to construct hierarchy data structures. The example I've posted below is for an XML file, which I use to construct a web page on the fly, but the hierarchy structure could be from the database. It doesn't matter really.
First, the XML file
Code:
<?xml version="1.0" encoding="utf-8"?>
<fragments>
<fragment>
<id>page</id>
<parent></parent>
<controller></controller>
<fragment>
<id>header</id>
<parent>page</parent>
<controller></controller>
</fragment>
<fragment>
<id>body</id>
<parent>page</parent>
<controller></controller>
<fragment>
<id>partition</id>
<parent>body</parent>
<controller></controller>
</fragment>
</fragment>
<fragment>
<id>footer</id>
<parent>page</parent>
<controller></controller>
</fragment>
</fragment>
</fragments>
Now the script,
PHP Code:
interface IWalker {
public function __construct( $children );
public function walk( IParser $parser );
}
class XmlWalker implements IWalker {
private $children;
public function __construct( $children ) {
if( !$children instanceof DomNodeList ) {
throw new IllegalParameterException( 'thrown exception on, expected parameter(s) not found.' );
break;
}
$this -> children = $children;
}
public function walk( IParser $parser ) {
foreach( $this -> children as $child ) {
if( $child instanceof DomElement ) {
$parser -> push( $child );
try {
$walker = new XmlWalker( $child -> childNodes );
$walker -> walk( $parser );
} catch( IllegalParameterException $e ) {
die( $e -> getMessage() );
}
}
}
}
}
class ApplicationFaultException extends Exception {
public function __construct( $message ) {
parent::__construct( $message );
}
}
class IllegalParameterException extends Exception {
public function __construct( $message ) {
parent::__construct( $message );
}
}
interface IParser {
public function getRoot();
public function push( $fragment ); // DomElement
}
class XmlAdaptableCompositeParser implements IParser {
private $decoratable;
public function __construct( $decoratable ) {
$this -> decoratable = $decoratable;
}
public function getRoot() {
return $this -> decoratable -> getRoot();
}
public function push( $fragment ) {
if( $fragment -> nodeName == 'fragment' ) {
return $this -> decoratable -> push( $fragment );
}
}
}
class XmlCompositeParser implements IParser {
private $root;
public function __construct( $fragment ) {
if( !$fragment instanceof DomElement ) {
throw new IllegalParameterException( 'thrown exception on, expected parameter(s) not found.' );
break;
}
$this -> root = new Composite( $fragment );
}
public function getRoot() {
return $this -> root;
}
public function push( $fragment ) {
$composite = new Composite( $fragment );
if( $tmp = $this -> traverse( $this -> getRoot(), $fragment ) ) {
$tmp -> attach( $composite );
}
}
private function traverse( $composite, $fragment ) {
$parent = $fragment -> getElementsByTagName( 'parent' );
$parent = $parent -> item( 0 ) -> nodeValue;
if( $composite -> getId() == $parent ) {
return $composite;
} else {
if( $composite -> hasChildren() ) {
$children = $composite -> getChildren();
foreach( $children as $child ) {
$c = new Composite( $fragment );
if( $tmp = $this -> traverse( $child, $fragment ) ) {
$tmp -> attach( $c );
}
}
}
}
}
}
class Composite extends Component {
public function __construct( $fragment ) {
if( !$fragment instanceof DomElement ) {
throw new IllegalParameterException( 'throw exception on, expected parameter(s) not found.' );
break;
}
parent::__construct( $fragment );
}
}
abstract class Component {
protected $id;
protected $parent;
protected $template;
protected $children = array();
protected $controller;
public function __construct( $fragment ) {
$this -> fragment = $fragment;
$this -> id = $this -> getNode( 'id' );
$this -> controller = $this -> getNode( 'controller' );
}
public function hasChildren() {
return count( $this -> children );
}
public function getChildren() {
return $this -> children;
}
public function getNode( $node ) {
$items = $this -> fragment -> getElementsByTagName( $node );
return $items -> item( 0 ) -> nodeValue;
}
public function setTemplate( $template ) {
$this -> template = $template;
}
public function getTemplate() {
return $this -> template;
}
public function getController() {
return $this -> controller;
}
public function getId() {
return $this -> id;
}
public function attach( $composite ) {
$this -> children[] = $composite;
}
}
And use it with this for example,
PHP Code:
$dom = new DomDocument;
$dom -> load( 'test.xml' );
$fragment = $dom -> documentElement -> childNodes;
$walker = new XmlWalker( $fragment -> item( 1 ) -> childNodes );
// require a decorator to filter tags by the name of 'fragment'
$parser = new XmlAdaptableCompositeParser( new XmlCompositeParser( $fragment -> item( 1 ) ) );
$walker -> walk( $parser );
echo( '<pre>' ); print_r( $parser -> getRoot() ); echo( '</pre>' );
EDIT: 22/01/06
If you find that you want to apply a number of filters prior to the controller, via a number of decorators, then you could take a what changes I made later;
Note that there is no controller node as such from earlier examples, as the controller it's self is applied as a filter as well.
Code:
...
<fragment>
<id>header</id>
<parent>page</parent>
<filters>
<filter name="PageController" pathname="pathname/to/file" />
<filter name="FilterOneController" pathname="pathname/to/file" />
<filter name="FilterTwoController" pathname="pathname/to/file" />
</filters>
</fragment>
...
Now, from XmlCompositeParser:
ush( $fragment ); you would need to do the following before you add the composite to the composite structure, it's self;
PHP Code:
// ...
$filters = $fragment -> getElementsByTagName( 'filters' );
$filters = $filters -> item( 0 );
$tmp = array();
if( $filters instanceof DomElement ) {
foreach( $filters -> childNodes as $filter ) {
if( $filter instanceof DomElement ) {
$name = $filter -> getAttribute( 'name' );
$pathname = $filter -> getAttribute( 'pathname' );
include_once( $pathname.strtolower( $name ).'.php' );
$tmp[] = $name;
}
}
}
$temp = array();
$cl = array_shift( $tmp );
$temp[] = $cl;
foreach( $tmp as $class ) {
$temp[] = new $class( array_shift( $temp ) );
}
$composite = array_shift( $temp ); // holds newly generated decorators
if( $tmp = $this -> traverse( $this -> getRoot(), $fragment ) ) {
$tmp -> attach( $composite );
}
For example, here is an example node, and it's output
Code:
...
<filters>
<filter name="Controller" pathname="controllers/" />
<filter name="ThirdController" pathname="controllers/" />
<filter name="SecondController" pathname="controllers/" />
<filter name="FirstController" pathname="controllers/" />
</filters>
...
Code:
Array
(
[0] => FirstController Object
(
[decorated:private] => SecondController Object
(
[decorated:private] => ThirdController Object
(
[decorated:private] => Controller
)
)
)
)
PHP Code:
class Controller {
private $decorated;
public function __construct() {
}
}
class FirstController {
private $decorated;
public function __construct( $decorated ) {
$this -> decorated = $decorated;
}
}
class SecondController {
private $decorated;
public function __construct( $decorated ) {
$this -> decorated = $decorated;
}
}
class ThirdController {
private $decorated;
public function __construct( $decorated ) {
$this -> decorated = $decorated;
}
}
The decoration therefore happens automatically for you, just remember though to set the ordering correctly, and it's better to implement a common Interface as well.
Yours, Dr Livingston
Bookmarks