The Composite Design Pattern in PHP: Part IV Composite Drawing

killeAppA Composite Drawing Program

In the original Gang of Four Composite example, (pp. 163-173 of Design Patterns) they showed a drawing program. It was broken down into separate leaves for Line, Rectangle, and Text. I included those leaves and added a Circle leaf. On the first rendering, I used .svg files, but that turned out to be far more limited and awkward than I wanted. Since PHP is not known as a language for doing graphics like ActionScript and Python (or Java for that matter), I wanted to use it for a dynamic engine for taking advantage of the HTML5 svg tag. To my surprise (and delight) I found that everything worked quite well with all of the browsers. I still only used subset of the six basic shapes provided in the SVG 1.1 W3 release. Text is considered a separate object apart from the basic shapes of:

  • rectangle
  • circle
  • ellipse
  • line
  • polyline
  • polygon

Before going further, it will help to look at the code and the example of employing the Composite pattern with Scalable Vector Graphics(SVG). Also, your might want to look at Part I, Part II and Part III of the Composite Pattern series on this blog. Use the Play button to see the composite SVG creation and click the Download button to get a zip file with all the code for the application;
PlayDownload

SVG Leaves

This Composite example employs the structure and most of the code from the full pattern example from Part III of this series. The major changes are to the leaf elements and rendering in the Composite. However, not a lot has changed otherwise, and that is one of the essential features of design patterns: re-use. First, take a look at the rectangle leaf class:

<?php
//Rectangle.php
class Rectangle extends IComponent
{
    private $xpos;
    private $ypos;
    private $wide;
    private $high;
    private $fill;
    private $stroke;
    private $strWidth;
 
    public function __construct($rx,$ry,$rw,$rh,$f,$sc,$sw)
    {
        $this->xpos=$rx;
        $this->ypos=$ry;
        $this->wide=$rw;
        $this->high=$rh;
        $this->fill=$f;
        $this->stroke=$sc;
        $this->strWidth=$sw;   
    }
 
    /* None of this batch of methods are used by shape or rectangle leaf */
    /* However in order to correctly implement the interface */
    /* you need some kind of implementation */
    public function add(IComponent $comOn){}
    public function remove(IComponent $comGone){}
    public function getChild($int){}
    protected function getComposite() {}
 
    /* Draw Rectangle */
    public function draw()
    {  
        echo "<rect x='$this->xpos' y='$this->ypos' width='$this->wide' height='$this->high' fill='$this->fill' stroke='$this->stroke' stroke-width='$this->strWidth' />";
    }
}
?>

It does not use most of the inherited abstract classes but implements them because the structure calls for it. The draw() method then sets up the SVG parameters for the rectangle shape. As you will see, the other leaf classes do the same thing in the continuing page:

<?php
//Circle.php
class Circle extends IComponent
{
    private $xpos;
    private $ypos;
    private $radius;
    private $fill;
    private $stroke;
    private $strWidth;
 
    public function __construct($cx,$cy,$r,$f,$sc,$sw)
    {
        $this->xpos=$cx;
        $this->ypos=$cy;
        $this->radius=$r;
        $this->fill=$f;
        $this->stroke=$sc;
        $this->strWidth=$sw;   
    }
 
    /* None of this batch of methods are used by the circle leaf */
    /* However in order to correctly implement the interface */
    /* you need some kind of implementation */
    public function add(IComponent $comOn){}
    public function remove(IComponent $comGone){}
    public function getChild($int){}
    protected function getComposite() {}
 
    /* Draw Circle */
    public function draw()
    {
        echo "<circle cx='$this->xpos' cy='$this->ypos' r='$this->radius' fill='$this->fill' stroke='$this->stroke' stroke-width='$this->strWidth' />";
    }
}
?>
 
<?php
//Line.php
class Line extends IComponent
{
    private $xpos1;
    private $ypos1;
    private $xpos2;
    private $ypos2;
    private $stroke;
    private $strWidth;
 
    public function __construct($x1,$y1,$x2,$y2,$sc,$sw)
    {
        $this->xpos1=$x1;
        $this->ypos1=$y1;
        $this->xpos2=$x2;
        $this->ypos2=$y2;
        $this->stroke=$sc;
        $this->strWidth=$sw;   
    }
 
    /* None of this batch of methods are used by the line leaf */
    /* However in order to correctly implement the interface */
    /* you need some kind of implementation */
    public function add(IComponent $comOn){}
    public function remove(IComponent $comGone){}
    public function getChild($int){}
    protected function getComposite() {}
 
    /* Draw Line */
    public function draw()
    {  
        echo "<g stroke='$this->stroke'>";
        echo "<line x1='$this->xpos1' y1='$this->ypos1' x2='$this->xpos2' y2='$this->ypos2' stroke-width='$this->strWidth' />";
        echo "</g>";
    }
}
?>
 
<?php
//Text.php
class Text extends IComponent
{
    private $x;
    private $y;
    private $fontFam;
    private $fontSiz;
    private $fontClr;
    private $text;
 
    public function __construct($tx,$ty,$ff,$fs,$fclr,$msg)
    {
        $this->x=$tx;
        $this->y=$ty;
        $this->fontFam=$ff;
        $this->fontSiz=$fs;
        $this->fontClr=$fclr;
        $this->text=$msg;
    }
 
    /* None of this batch of methods are used by the text leaf */
    /* However, in order to correctly implement the interface */
    /* you need some kind of implementation */
    public function add(IComponent $comOn){}
    public function remove(IComponent $comGone){}
    public function getChild($int){}
    protected function getComposite() {}
 
    /* Format text object */
    public function draw()
    {  
        echo "<text x='$this->x' y='$this->y' font-family='$this->fontFam' font-size='$this->fontSiz' fill='$this->fontClr' >";
        echo $this->text;
        echo "</text>";
    }
}
?>

All that has to be done in each class is to provide the code to render the SVG graphic or text object. All of the working code is in a single method; draw(). (In Parts I-III, the equivalent method was called operation().) Further, only between 1 and 3 lines of code are required in the draw() method to create the desired shape/text object.

About the only other feature is the use of multiple parameters in each of the constructors. Each constructor function has arguments unique to the SVG object being used based on the tag’s native parameters. (e.g., cx and cy for the Circle element). The arguments are passed from the client and placed into private variables both for purposes of encapsulating the data and for using the values throughout the class. In this example, the client class is Engine that will be covered further on in this post, but first take a look at the Composite class.

Same Old Composite

In looking at the Composite class, you might be thinking that it looks a lot like the Composite from Part III of this series. That’s because other than having a method named draw() instead of operation(), it is almost identical. The details of the draw() operation reflect using SVG graphic elements, but it too is pretty much the same. That’s because

…one of the main purposes of design patterns is to promote re-use.

Now, in looking at the Composite code, the focal point is just on the new materials in the draw() operation and not in a re-invented wheel. The wheel is the same; just a few spokes have changed. The array name is changed from aChildren() to artWork() to better reflect the application’s purpose of creating composite graphics from different SVG components.

<?php
//Composite.php
class Composite extends IComponent
{
    private $artWork;
 
    public function __construct()
    { 
 
        $this->artWork=array();
    }
 
    public function add(IComponent $comOn)
    {
        array_push($this->artWork,$comOn);
        $comOn->setParent($this);
    }
 
    public function remove(IComponent $comGone)
    {
        if($comGone === $this)
        {
            for($countA = 0; $countA < count($this->artWork); $countA++)
            {
                $this->safeRemove($this->artWork[$countA]);
            }
            $this->artWork= array();
            $this->removeParentRef();
        }
        else
        {
            for($countB = 0; $countB < count($this->artWork); $countB++)
            {
                if($this->artWork[$countB] == $comGone)
                {
                    $this->safeRemove($this->artWork[$countB]);
                    array_splice($this->artWork, $countB, 1);
                }
            }
        }    
    }
 
    private function safeRemove(IComponent $safeCom)
    {
        if($safeCom->getComposite())
        {
            $safeCom->remove($safeCom);
        }
        else
        {
            $safeCom->removeParentRef();
        }
    }
 
    protected function getComposite()
    {
        return $this;
    }
 
    public function getChild($int)
    {
        if(($int > 0) && ($int <= count($this->artWork)))
        {
            return $this->artWork[$int-1];
        }
        else
        {
            return null;
        }   
    }
 
    //Note: The following method is recursive
    public function draw()
    {
        echo "<!doctype html><html><head>";
        echo "<link rel='stylesheet' href='composite.css'>";
        echo "<meta charset='UTF-8'></head><body>";
        echo "<svg width='60%' height='25%' xmlns='http://www.w3.org/2000/svg' version='1.1'>";
        foreach($this->artWork as $elVal)
        {
            $elVal->draw();
        }
        echo "</svg>";
        echo "</body></html>";
    }
}
?>

The boundaries of SVG graphics is a very long subject unto a world of its own. I can only refer you to the original source for the W3C’s latest recommendation. For now, suffice it to say that not only the width and height attributes of an SVG element are crucial, but I intentionally left out the whole viewBox attribute so as not to get bogged down in trying to explain a very complex system of mapping vector graphics while showing how it works within a Composite design pattern. More on this issue at the end of this post. For now, focus on the simpler features of creating what you want to see on the Web page using SVG graphic simple shapes and text.

The Client: Engine

The Client is an integral part of the Composite design pattern, and after making this latest example, I think that using unique names for each graphic/text rclient might be a good idea. That’s why I named this client, Engine—it draws a toy train engine (…the limits of my artistic skills). With more complex graphics, the client might need helper classes for each component so as not to create a monster client class every time you sit down to create a composite graphic. It creates two toy train engines using two composites that are almost identical except for some color and text changes.

<?php
//Engine.php
ERROR_REPORTING( E_ALL | E_STRICT );
ini_set("display_errors", 1);
function __autoload($class_name) 
{
    include $class_name . '.php';
}
//Engine is a Client class
class Engine
{
    private $rootCompos;
 
    /* Parameters and color scheme
    Circle:$cx,$cy,$r,$f (color),$sc(color),$sw
    Rectangle:$rx,$ry,$rw,$rh,$f(color),$sc(color),$sw
    Line: $x1,$y1,$x2,$y2,$sc (color),$sw
    Text:$tx,$ty,$ff (font family),$fs (font size),$fclr (color),$msg
 
    6AA3D8 (lt med blue) 526F8F (blue grey) F26E24 (bright burnt orange)
    CFD7D9 (cement gray) 0B0B0D (close to black)*/
 
    public function __construct()
    {
        //Create 2 Composites and 2 composite graphics using getChild()
        $this->rootCompos=new Composite();       
        $this->rootCompos->add(new Composite());
        $this->rootCompos->getChild(1)->add(new Line(78,208,134,138,'#526fbf',14));
        $this->rootCompos->getChild(1)->add(new Circle((130),85,16,'#6AA3D8','#0B0B0D',1));
        $this->rootCompos->getChild(1)->add(new Rectangle(221,28,24,42,'#CFD7D9','#0B0B0D',1));
        $this->rootCompos->getChild(1)->add(new Rectangle(128,68,209,77,'#F26E24','none','none'));
        $this->rootCompos->getChild(1)->add(new Rectangle(128,145,281,24,'#0B0B0D','none','none'));
        $this->rootCompos->getChild(1)->add(new Rectangle(337,20,72,129,'#6AA3D8','none','none'));
        $this->rootCompos->getChild(1)->add(new Rectangle(327,6,92,14,'#0B0B0D','none','none'));
        $this->rootCompos->getChild(1)->add(new Rectangle(128,0,42,68,'#0B0B0D','none','none'));
        $this->rootCompos->getChild(1)->add(new Circle(168,168,40,'#CFD7D9','#526F8F',5));
        $this->rootCompos->getChild(1)->add(new Circle(338,168,40,'#CFD7D9','#526F8F',5));
        $this->rootCompos->getChild(1)->add(new Circle(168,168,10,'#F26E24','none','none'));
        $this->rootCompos->getChild(1)->add(new Circle(340,168,10,'#F26E24','none','none'));
        $this->rootCompos->getChild(1)->add(new Circle(373,50,16,'#0B0B0D','none','none'));
        $this->rootCompos->getChild(1)->add(new Line(3,210,500,210,'#0B0B0D',4));
        $this->rootCompos->getChild(1)->add(new Text(138,110,'Arial Black','16','#0B0B0D','Composite Zephyr #1'));
 
        $this->rootCompos->add(new Composite());
        $this->rootCompos->getChild(2)->add(new Line(78,208,134,138,'#0B0B0D',14));
        $this->rootCompos->getChild(2)->add(new Circle((130),85,16,'#CFD7D9','#0B0B0D',1));
        $this->rootCompos->getChild(2)->add(new Rectangle(221,28,24,42,'#CFD7D9','#0B0B0D',1));
        $this->rootCompos->getChild(2)->add(new Rectangle(128,68,209,77,'#6AA3D8','none','none'));
        $this->rootCompos->getChild(2)->add(new Rectangle(128,145,281,24,'#0B0B0D','none','none'));
        $this->rootCompos->getChild(2)->add(new Rectangle(337,20,72,129,'#F26E24','none','none'));
        $this->rootCompos->getChild(2)->add(new Rectangle(327,6,92,14,'#0B0B0D','none','none'));
        $this->rootCompos->getChild(2)->add(new Rectangle(128,0,42,68,'#0B0B0D','none','none'));
        $this->rootCompos->getChild(2)->add(new Circle(168,168,40,'#CFD7D9','#526F8F',5));
        $this->rootCompos->getChild(2)->add(new Circle(338,168,40,'#CFD7D9','#526F8F',5));
        $this->rootCompos->getChild(2)->add(new Circle(168,168,10,'#F26E24','none','none'));
        $this->rootCompos->getChild(2)->add(new Circle(340,168,10,'#F26E24','none','none'));
        $this->rootCompos->getChild(2)->add(new Circle(373,50,16,'#0B0B0D','none','none'));
        $this->rootCompos->getChild(2)->add(new Line(3,210,500,210,'#0B0B0D',4));
        $this->rootCompos->getChild(2)->add(new Text(138,110,'Arial Black','16','#0B0B0D','Composite Zephyr #2'));
 
        $this->rootCompos->draw();
 
    }
}
$worker=new Engine();
?>

Most of the work in getting the program up and running was in getting each leaf object correctly set up with the right parameters, and working out the vectors for the graphic in the Engine class. Otherwise, it was pretty easy. The program itself was simply re-using the structure for the full composite developed in Part III of this series.

Can PHP and SVG be used to Create Games?

After completing the MOOC course on Python offered by Rice University, I’ve been trying to work out a way to use PHP for creating games using OOP and design patterns. The games created using Python were based on an IDE written in JavaScript, and the classic games we created included arcade games; specifically Pong and Astroids. Using modular arithmetic, a lot of the knotty algorithms were easily solved, but as you may have seen in the series on the Part I of PHP Game Making, the modulus operators were different. However, using workarounds, it was possible to create a simple interactive game (with a robot player.) Further using a State design pattern, we eventually worked out a two-player game that could be remotely played.

In some upcoming posts, I’d like to look further into using SVG graphics in conjunction with PHP and looking into some gaming features that we can use in Web-based games developed in PHP. Naturally, design patterns will be the tools of choice in making these games.

Share

Copyright © 2013 William Sanders. All Rights Reserved.

0 Responses to “The Composite Design Pattern in PHP: Part IV Composite Drawing”


  • No Comments

Leave a Reply