Приглашаем посетить
Литература 20 век (20v-euro-lit.niv.ru)

Hack 51. Create Modular Interfaces

Previous
Table of Contents
Next

Hack 51. Create Modular Interfaces

Hack 51. Create Modular Interfaces Hack 51. Create Modular Interfaces

Use dynamic loading to allow users to write snap-in modules for your application.

Most of the really popular PHP open source applications have an extension mechanism that allows for PHP coders to write small fragments of code that are dynamically loaded into the application. This hack demonstrates an XML-based drawing script that you can extend simply by placing new PHP classes into a modules directory; of course, the point is not as much the drawing code as the way you can extend it easily.

6.2.1. The Code

Save the code in Example 6-1 as modhost.php.

Example 6-1. The code that handles a modular PHP architecture
<?php 
class DrawingEnvironment 
{
	private $img = null;
	private $x = null;
	private $y = null;
	private $colors = array();

	public function __construct( $x, $y )
	{
		$this->img = imagecreatetruecolor( $x, $y );
		$this->addColor( 'white', 255, 255, 255 );
		$this->addColor( 'black', 0, 0, 0 );
		$this->addColor( 'red', 255, 0, 0 );
		$this->addColor( 'green', 0, 255, 0 );
		$this->addColor( 'blue', 0, 0, 255 );
		
		imagefilledrectangle( $this->image(),
			0, 0, $x, $y, $this->color( 'white' ) );
	}

	public function image() { return $this->img; }
	public function size_x() { return $this->x; }
	public function size_y() { return $this->y; }
	public function color( $c ) { return $this->colors[$c]; }

	public function save( $file )
	{
		imagepng( $this->img, $file );
	}

	protected function addColor( $name, $r, $g, $b )
	{
		$col = imagecolorallocate($this->img, $r, $g, $b);
		$this->colors[ $name ] = $col;
	}
}
interface DrawingObject
{
	function drawObject( $env ); 
	function setParam( $name, $value );
}

function loadModules( $dir )
{
	$classes = array();

	$dh = new DirectoryIterator( $dir );
	foreach( $dh as $file )
	{
	if( $file->isDir() == 0 && preg_match( "/[.]php$/", $file ) )
	{
		include_once( $dir."/".$file );
		$class = preg_replace( "/[.]php$/", "", $file );
		$classes []= $class;
	}
}

return $classes; 
}
$classes = loadModules( "mods" );

$dom = new DOMDocument();
$dom->load( $argv[1] );
$nl = $dom->getElementsByTagName( "image" );
$root = $nl->item( 0 );

$size_x = $root->getAttribute( 'x' );
$size_y = $root->getAttribute( 'y' );
$file = $root->getAttribute( 'file' );

$de = new DrawingEnvironment( $size_x, $size_y );

$obs_spec = array();

$el = $root->firstChild;
while( $el != null )
{
	if ( $el->tagName != null )
	{
		$params = array();
		for( $i = 0; $i < $el->attributes->length; $i++ )
	{
		
		$p = $el->attributes->item( $i )->nodeName;
		$v = $el->attributes->item( $i )->nodeValue;
		$params[ $p ] = $v;
	}

	$obs_spec []= array(
		'type' => $el->tagName,
		'params' => $params
	);
}
$el = $el->nextSibling;
}

foreach( $obs_spec as $os )
{
	$ob = null;
	eval( '$ob = new '.$os['type'].'();' ); 
	foreach( $os[ 'params' ] as $key => $value )
		$ob->setParam( $key, $value ); 
	$ob->drawObject( $de ); 
}

$de->save( $file ); 
?>

Save the code in Example 6-2 as mods/Circle.php.

Example 6-2. An example module that draws circles
<?php 
class Circle implements DrawingObject 
{
	private $radius = null;
	private $color = null;
	private $x = null;
	private $y = null;

	function drawObject( $env )
	{
		$r2 = $this->radius / 2;
		imagefilledellipse( $env->image(),
			$this->x - $r2, $this->y - $r2,
			$this->radius, $this->radius,
			$env->color( $this->color )
		);
	}

	function setParam( $name, $value )
	{
		if ( $name == "radius" ) $this->radius = $value;
		if ( $name == "color" ) $this->color = $value;
		if ( $name == "x" ) $this->x = $value;
		if ( $name == "y" ) $this->y = $value;
	}
}
?>

6.2.2. Running the Hack

This hack is run on the command line. The first thing to do is to create an XML test file:

	<image x='100' y='100' file='out.png'>
		<Circle x='20' y='40' color='red' radius='15' />
		<Circle x='60' y='30' color='green' radius='30' />
		<Circle x='70' y='75' color='blue' radius='35' />
	</image>

This XML file specifies that the image should be 100x100 pixels and named out.png, and that the image should have three circles, each of varying size and color.

With the XML in hand, run the script:

	% php modhost.php test.xml

The first argument to the script is the name of the XML file that contains the image specifications. The output image file looks like Figure 6-1.

Figure 6-1. The output image
Hack 51. Create Modular Interfaces


To explain a little about what happened here, let me start with the modhost.php file. At the start of the file, I've defined the DrawingEnvironment class, which is just a wrapper around an image with a few accessors. This environment will be passed to any drawing objects so that those objects can paint into the image. The next point of interest is the DrawingObject interface, which objects must conform to for drawing.

The real trick comes in the loadModules() function, which loads all of the modules from the specified directory into the PHP environment. Then the script reads the XML file supplied to it, and parses it into the $obs_spec object, which is an array version of the XML file. The next step is to create the drawing environment and build the drawing objects based on the $obs_ spec values; these values are then rendered into the image. Finally, the image is stored to a file.

Figure 6-2 shows the relationships between the DrawingEnvironment and the DrawingObjects, as well as how the dynamically loaded Circle class implements the DrawingObject interface.

Figure 6-2. The structure of the drawing system
Hack 51. Create Modular Interfaces


This is a simple illustration of this technique.

Hack 51. Create Modular Interfaces

The specification of an interface makes this a PHP 5-specific script, but the include_once() and eval() functions were in PHP 4; it would take a bit of modification, but there is no reason that you can't do something similar to this in PHP 4.


I strongly recommend adding an extension mechanism such as this to any reasonably sized PHP application, especially when you expect deployment in multiple environments that you don't control. This approach gives end users the ability to customize the program to their requirements, without you having to go in and alter code directly for every new feature or object type.

6.2.3. See Also


Previous
Table of Contents
Next