<?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;
}
}