Location: PHPKode > scripts > Class Generator > classgenerator/library/Util/ClassGenerator.php
<?php
/**
 * ClassGenerator.php
 * Copyright (C) 2008  Rottensteiner Stefan
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; Version 2.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * ===============================================================================
 * @version 1.0
 * @author Rottensteiner Stefan
 * @licence GPL v2
 */
 
/**
 * This class generates PHP (.x) class just with simple XML descriptions.
 * Depends on the standard PHP extension XMLReader.
 * @author Rottensteiner Stefan 
 * @copyright Rottensteiner Stefan
 */
Class Util_ClassGenerator {
	/**
	 * Use this XML namespace for generator specific elements and attrbiutes
	 * Value is <b>http://www.example.com/namespaces#ClassGenerator</b>
	 * @var string
	 */
	const XMLNS = 'http://www.example.com/namespaces#ClassGenerator';

	/**
	 * The built in prefix for our own namespace
	 * Value is <b>cgen</b>
	 * @var string
	 */
	const XMLNS_PREFIX 	= 'cgen';
	
	/**
	 * PHP 5 style constructor
	 */
	public function __construct(){
	}
	
	/**
	 * Create class(es) from a proper XML description
	 * $XMLSource may be:<br>
	 * 1. The whole XML description as string<br>
	 * 2. A previously created instance of the XMLReader
	 * @param mixed $XMLSource
	 * @return Object
	 */
	public function & createFromXML( $XMLSource ){
		if (empty($XMLSource))
		    throw new Exception('Empty XML source');
		    
		$class = null;
		if (is_string($XMLSource)) {
		    $reader = new XMLReader();
		    $reader->XML($XMLSource);
		}
		else if (is_object( $XMLSource ) && (is_a($XMLSource,'XMLReader') || is_subclass_of( $XMLSource,'XMLReader'))){
		    $reader = & $XMLSource;
		}
		else
		    throw new Exception('Unknown XML source');

		$class = & $this->generate($reader);
		
		$reader->close();
		return $class;
	}

	protected function & generate($xml){
		// The generated class
	    $class = null;
	    // Jump to the first element
	    if (!$this->fastForward($xml,XMLReader::ELEMENT))
	        return $class;
		// Don't trick me ..
		$className = preg_replace('/[^a-z0-9\_]/i','',trim($xml->localName));
		$isEmptyElement = (bool) $xml->isEmptyElement;
		$attributes = array();
		if ($xml->hasAttributes) {
		    while($xml->moveToNextAttribute()) {

		        if ($xml->prefix == 'xmlns')
		            continue;
				if ($xml->namespaceURI == self::XMLNS || $xml->prefix == self::XMLNS_PREFIX)
				    $attName = self::XMLNS_PREFIX.':'.$xml->localName;
				else
					$attName = $xml->localName;
		    	$attributes[$attName ] = $xml->value;
		    }
			$xml->read();
		}
		/*
		 * Playground for may, many lines of codes ...
		 */
		if (!class_exists($className)) {

			$classExtends = '';
		    if (!empty($attributes[self::XMLNS_PREFIX.':extends'])){
		        $classExtends = 'extends '. $attributes[self::XMLNS_PREFIX.':extends'];
		        // Don't trick me ..
		        $classExtends = preg_replace('/[^a-z0-9_]i/','', $classExtends);
	  		}

			$classImplements = '';
		    if (!empty($attributes[self::XMLNS_PREFIX.':implements'])) {
				$classImplements = 'implements '. $attributes[self::XMLNS_PREFIX.':implements'];
				// Don't trick me ..
				$classImplements = preg_replace('/[^a-z0-9_]i/','', $classImplements);
			}

			// Class genesis
		    eval  ("
			Class {$className} {$classExtends} {$classImplements} {
			};
			");
		}
		
		$reflectionClass = new ReflectionClass($className);
		if ($reflectionClass->isInstantiable())
			$class = new $className();
		else {
		    // No instance method given
		    if (empty($attributes[ self::XMLNS_PREFIX.':factMethod']))
		    	throw new Exception('Class "'.$className.'" is not instantiable');
			$factMethod = $attributes[ self::XMLNS_PREFIX.':factMethod'];
			if (empty($attributes[ self::XMLNS_PREFIX.':factClass']))
			    $reflectionFactClass = & $reflectionClass;
			else
			    $reflectionFactClass = new ReflectionClass($attributes[ self::XMLNS_PREFIX.':factClass']);
			// Create class by instan method
			$reflectionMethod = $reflectionFactClass->getMethod($factMethod);
			// Call static factory method
			if ($reflectionMethod->isStatic())
				$class= $reflectionMethod->invoke(null, $className);
			else
				throw new Exception("Non static
					factory method'".$attributes[ self::XMLNS_PREFIX.':factMeth']."'
					of class '{$className}'");
		}

		// Import attributes - if there were any
		if (!empty($attributes))
			$this->importAttributes($attributes, $class);
		// Everything read?
		if ($isEmptyElement)
		    return $class;
		// FF to the firs child
		while ($xml->nodeType != XMLReader::ELEMENT && $xml->nodeType != XMLReader::END_ELEMENT)
		    $xml->read();
  		// No child found, so return the class
		if ($xml->nodeType != XMLReader::ELEMENT)
		    return $class;
		// Run throgh the children..
		do {
			$elementName = $xml->localName;
			$childClass = $this->generate($xml);
			$this->importChildClass($elementName, $childClass, $class);
			// FF to the next child or end of current class
			while ($xml->nodeType != XMLReader::ELEMENT && $xml->nodeType != XMLReader::END_ELEMENT)
		    	$xml->read();
		} while ($xml->nodeType!=XMLReader::NONE && $xml->nodeType != XMLReader::END_ELEMENT);
		// Overread end element so we can step back and go processing the next nodes
		if ($xml->nodeType == XMLReader::END_ELEMENT)
		    $xml->read();
	    return $class;
	}
	
	/**
	 * Import an child into a parent class
	 * @param string $elementName Qualified XML element name
	 * @param object $childClass The new child
	 * @param object $parentClass Parent for the child
	 * @return boolean
	 */
	protected function importChildClass( $elementName, & $childClass, & $parentClass ){
		return $this->setClassProperty($elementName, $childClass, $parentClass);
	}
	
	/**
	 * Import a list of attrbiutes
	 * @param array $attributes
	 * @param object $parentClass Parent for the child
	 * @return boolean
	 */
	protected function importAttributes(& $attributes, & $parentClass ){
	    foreach ($attributes as $attName => $attValue){
	        // Delete because processed
	        unset($attributes[$attName]);
	        $success = $this->setClassProperty($attName, $attValue, $parentClass);
		}
	}
	
	/**
	 * Common method to set class properties.
	 * This method is used by <b>importAttributes</b> and <b>importChildClass</b>.
	 * @param string $elementName Name of the property, means the local
	 *      name of the XML element
	 * @param mixed $elementValue Value of this property
	 * @param object $parentClass The class the method should set the property
	 * @return boolean
	 */
	protected function setClassProperty($elementName, & $elementValue,  & $parentClass ){

		$reflectionClass = new ReflectionClass( $parentClass );
		$className = get_class($parentClass);
	    
		$method = 'set' . UCFirst($elementName);
		if ($reflectionClass->hasMethod($method)){
			$reflectionMethod = $reflectionClass->getMethod($method);
			if (!$reflectionMethod->isAbstract() && !$reflectionMethod->isDestructor()){
			    if ($reflectionMethod->isPublic()) {
			    	$reflectionMethod->invoke($parentClass, $elementValue );
			        return TRUE;
				}
			}
		}
		$method = 'add' . UCFirst($elementName);
		if ($reflectionClass->hasMethod($method)){
			$reflectionMethod = $reflectionClass->getMethod($method);
			if (!$reflectionMethod->isAbstract() && !$reflectionMethod->isDestructor()){
			    if ($reflectionMethod->isPublic()) {
			    	$reflectionMethod->invoke($parentClass, $elementValue );
			        return TRUE;
				}
			}
		}
		
		$wrapperClassName = $className.'_AutomagicWrapper';
		$wrapperClassDefinition = '
		if (!class_exists($wrapperClassName)) {
			Class ${className}_AutomagicWrapper extends ${className} {
				public static function & setProtectedProperty($name,$value, & $class ){
					$class->$name = $value;
					return $class;
				}
			}
		}';
		eval( str_replace('${className}', get_class($parentClass) , $wrapperClassDefinition ));

		$propertyName = $elementName;
		if ($reflectionClass->hasProperty($propertyName)){
		    $reflectionProperty= $reflectionClass->getProperty($propertyName);
		    if ($reflectionProperty->isPublic()){
		        // Public property
		        $reflectionProperty->setValue($parentClass, $elementValue);
		        return TRUE;
			}
			else if ($reflectionProperty->isProtected()){
			    // Protected property
			    eval ($wrapperClassName.'::setProtectedProperty($propertyName,$elementValue,$parentClass);');
			    return TRUE;
			}
			else {
			    // Protected property
			    throw new Exception('Cannot set private property "'.$propertyName.'" in class "'.get_class($parentClass).'"');
			}
		}
		return FALSE;
	}
	
	/**
	 * An internal helper method to read until the next "valid" XML element.
	 * @param XMLReader $xml Currently used XMLReader
	 * @param int $expectedNodeType The Method will read forward to the next element
	 *      of this type
	 * @return boolean
	 */
	protected function fastForward(XMLReader $xml, $expectedNodeType = XMLReader::NONE){
		$success = TRUE;
		// Vorspulen bis zum nächsten Element
		while(
			$xml->nodeType == XMLReader::NONE
			|| ( !empty($expectedNodeType) && $xml->nodeType != $expectedNodeType)
		 )  {
		    $success = $xml->read();
		    // Schon am Ende? Dann wieder retour.
		    if (!$success)
		        return FALSE;
		}
		return $success;
	}
}

Return current item: Class Generator