<?php
/**
* "Takes an XML file as input, parses it and sets parsed values as field values for a given class
* instance."
* @author: Claudius Tiberiu Iacob <hide@address.com>
* @license: Creative Commons Attribution Share Alike - Claudius Tiberiu Iacob 2009
*/
class ParamsProxy {
const ILLEGAL_ROOT_ELEMENT = "ILLEGAL_ROOT_ELEMENT";
const FOREIGN_CONFIG_FILE = "FOREIGN_CONFIG_FILE";
const EMPTY_ROOT_ELEMENT = "EMPTY_ROOT_ELEMENT";
const ILLEGAL_PARAM_ELEMENT = "ILLEGAL_PARAM_ELEMENT";
const EMPTY_PARAM_ELEMENT = "EMPTY_PARAM_ELEMENT";
const ILLEGAL_PROPERTY = "ILLEGAL_PROPERTY";
const TOO_FEW_PROPERTIES = "TOO_FEW_PROPERTIES";
const TOO_MANY_PROPERTIES = "TOO_MANY_PROPERTIES";
const INVALID_PROPERTY_NAME = "INVALID_PROPERTY_NAME";
const INVALID_PROPERTY_VALUE = "INVALID_PROPERTY_VALUE";
const ILLEGAL_ITEM_ELEMENT = "ILLEGAL_ITEM_ELEMENT";
const TOO_MANY_ITEM_ATTRIBUTES = "TOO_MANY_ITEM_ATTRIBUTES";
const ILLEGAL_ITEM_ELEMENT_ATTRIBUTE_NAME = "ILLEGAL_ITEM_ELEMENT_ATTRIBUTE_NAME";
const ILLEGAL_ITEM_ELEMENT_ATTRIBUTE_VALUE = "ILLEGAL_ITEM_ELEMENT_ATTRIBUTE_VALUE";
const EMPTY_ITEM_ELEMENT = "EMPTY_ITEM_ELEMENT";
const ILLEGAL_STRING_FOR_BOOLEAN_DEFINITION = "ILLEGAL_STRING_FOR_BOOLEAN_DEFINITION";
const ILLEGAL_STRING_FOR_NUMBER_DEFINITION = "ILLEGAL_STRING_FOR_NUMBER_DEFINITION";
const INVALID_CONFIG_PATH = "INVALID_CONFIG_PATH";
const NOT_ABSOLUTE_CONFIG_PATH = "NOT_ABSOLUTE_CONFIG_PATH";
const CONFIG_PATH_UNDER_DOC_ROOT = "CONFIG_PATH_UNDER_DOC_ROOT";
const CONFIG_PATH_UNDER_INCLUDES_PATH = "CONFIG_PATH_UNDER_INCLUDES_PATH";
const MISSING_CONFIG_FILE = "MISSING_CONFIG_FILE";
const INVALID_CONFIG_FILE = "INVALID_CONFIG_FILE";
const MISSING_REQUIRED_NAME = "MISSING_REQUIRED_NAME";
const MISSING_REQUIRED_VALUE = "MISSING_REQUIRED_VALUE";
protected $configFolderPath = null;
private $ownClassName = "ParamsProxy";
private $ownConfigFile = "ParamsProxy_config.xml";
private $configFolderParamName = "configurationFolder";
private $clientConfigFileName = "config.xml";
private $params = array ();
private $isInitialized = false;
private $rootElementName = "config";
private $rootElementType = 1;
private $rootBindAttributeName = "for";
private $paramElementType = 1;
private $paramElementName = "param";
private $propertyElementType = 1;
private $propertyElementName_name = "name";
private $propertyElementName_type = "type";
private $propertyElementName_value = "value";
private $propertyElementValue_string = "string";
private $propertyElementValue_number = "number";
private $propertyElementValue_boolean = "boolean";
private $propertyElementValue_array = "array";
private $propertyElementValue_null = "null";
private $propertyElementValue_auto = "auto";
private $propertyElementName_item = "item";
private $attributeName_key = "key";
private $attributeName_type = "type";
private $attributeValue_string = "string";
private $attributeValue_number = "number";
private $attributeValue_boolean = "boolean";
private $attributeValue_null = "null";
private $attributeValue_auto = "auto";
private static $instance = null;
public static function getInstance() {
if (!self::$instance instanceof self) {
self::$instance = new self();
}
return self::$instance;
}
public function configure($instance) {
$this->doConfigure ($instance);
}
protected function __construct () {
$this->provideDocumentRoot ();
$ownConfigString = $this->getFileContent ($this->ownConfigFile);
$ownConfigData =& $this->parseXML ($ownConfigString);
$this->params =& $this->getParams ($ownConfigData);
$this->configFolderPath = $this->params [$this->configFolderParamName];
$this->configFolderPath = $this->proofConfigFolderPath ($this->configFolderPath);
$this->isInitialized = true;
}
private function __clone () {
}
private function __wakeup() {
}
private function doConfigure ($instance) {
if ($this->isInitialized) {
$this->clientClsName = $this->getClassName($instance);
$clientConfFile = $this->configFolderPath . "/" . $this->clientClsName . "/" .
$this->getConfigFileName();
$clientConfStr = $this->getFileContent ($clientConfFile);
$clientConfData =& $this->parseXML ($clientConfStr);
$clientParams =& $this->getParams ($clientConfData);
$this->applyParams($instance, $clientParams);
}
}
private function getClassName ($instance) {
$ret = array ();
$name = get_class($instance);
preg_match('/\w+$/', $name, $ret);
return $ret[0];
}
protected function getConfigFileName () {
return $this->clientConfigFileName;
}
protected function applyParams ($targetInstance, $params) {
foreach ($params as $name => $value) {
$setterName = "set" . ucwords($name);
$targetInstance->$setterName($value);
}
}
private function provideDocumentRoot () {
if (!isset ($_SERVER['DOCUMENT_ROOT'])) {
if (isset ($_SERVER['SCRIPT_FILENAME'])) {
$_SERVER['DOCUMENT_ROOT'] = str_replace ( '\\', '/',
substr ($_SERVER['SCRIPT_FILENAME'], 0, 0 - strlen ($_SERVER['PHP_SELF'])));
}
}
if (!isset ($_SERVER['DOCUMENT_ROOT'])) {
if (isset($_SERVER['PATH_TRANSLATED'])) {
$_SERVER['DOCUMENT_ROOT'] = str_replace ( '\\', '/',
substr (str_replace ('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0,
0 - strlen ($_SERVER['PHP_SELF'])));
}
}
}
private function getFileContent ($filePath) {
if (!$this->isInitialized) {
$current = __FILE__;
$filePath = dirname($current) . DIRECTORY_SEPARATOR . $filePath;
}
$ret = @file_get_contents($filePath);
if ($ret === false) {
trigger_error(ParamsProxy::MISSING_CONFIG_FILE, E_USER_ERROR);
}
return $ret;
}
private function getParams ($data) {
$ret = array ();
$rootNode =& $this->parseRootNode ($data);
if (!$rootNode->hasChildNodes()) {
trigger_error(ParamsProxy::EMPTY_ROOT_ELEMENT, E_USER_ERROR);
}
$count = $rootNode->childCount;
if (!$this->isInitialized) {
if ($count > 1) {
trigger_error(INVALID_CONFIG_FILE, E_USER_ERROR);
}
}
$children =& $rootNode->childNodes;
for ($i = 0; $i < $count; $i++) {
$child =& $children[$i];
if ($child->nodeType == $this->paramElementType) {
if ($child->nodeName !== $this->paramElementName) {
trigger_error(ParamsProxy::ILLEGAL_PARAM_ELEMENT, E_USER_ERROR);
}
$result = &$this->parseParamNode($child);
if (!$this->isInitialized) {
if($result[$this->propertyElementName_name] !==
$this->configFolderParamName) {
trigger_error(INVALID_CONFIG_FILE, E_USER_ERROR);
}
}
$ret[$result[$this->propertyElementName_name]] = &$result[$this->propertyElementName_value];
}
}
return $ret;
}
private function proofConfigFolderPath ($path) {
$realPath = realpath ($path);
if ($realPath === false) {
trigger_error(ParamsProxy::INVALID_CONFIG_PATH, E_USER_ERROR);
}
$path = preg_replace('/\x5c{1,}/', '/', $path);
$path = preg_replace('/\x2f$/', '', $path);
$realPath = preg_replace('/\x5c{1,}/', '/', $realPath);
$realPath = preg_replace('/\x2f$/', '', $realPath);
if ($path !== $realPath) {
trigger_error(ParamsProxy::NOT_ABSOLUTE_CONFIG_PATH, E_USER_ERROR);
}
$docRoot = $_SERVER['DOCUMENT_ROOT'];
if (strpos($path, $docRoot) === 0) {
trigger_error(ParamsProxy::CONFIG_PATH_UNDER_DOC_ROOT, E_USER_ERROR);
}
$includePaths = ini_get('include_path');
$paths = array();
if (preg_match('/\x5c|;\w:/', $includePaths) === 1) {
$paths = explode(";", $includePaths);
} else {
$paths = explode(":", $includePaths);
}
foreach ($paths as $testPath) {
$testPath = realpath($testPath);
$testPath = preg_replace('/\x5c{1,}/', '/', $testPath);
$testPath = preg_replace('/\x2f$/', '', $testPath);
if ($testPath === "") {
continue;
}
if (strpos($path, $testPath) === 0) {
trigger_error (ParamsProxy::CONFIG_PATH_UNDER_INCLUDES_PATH, E_USER_ERROR);
}
}
return $path;
}
private function parseXML ($xmlString) {
$data = new DOMIT_Document();
DOMIT_DOMException::setErrorMode(DOMIT_ONERROR_DIE);
$success = $data->parseXML($xmlString, false);
if (!$success) {
trigger_error (ParamsProxy::INVALID_CONFIG_FILE . "\n" . $data->getErrorCode() .
": " . $data->getErrorString(), E_USER_ERROR);
}
return $data;
}
private function parseRootNode ($data) {
$rootNode =& $data->documentElement;
if (!$rootNode ||
$rootNode->nodeName !== $this->rootElementName ||
$rootNode->nodeType !== $this->rootElementType ||
!$rootNode->hasAttribute($this->rootBindAttributeName)) {
trigger_error(ParamsProxy::ILLEGAL_ROOT_ELEMENT, E_USER_ERROR);
}
if (!$rootNode->hasAttribute($this->rootBindAttributeName)) {
trigger_error(ParamsProxy::ILLEGAL_ROOT_ELEMENT, E_USER_ERROR);
}
$attributes =& $rootNode->attributes;
$attrLength = $attributes->getLength();
if ($attrLength > 1) {
trigger_error(ParamsProxy::ILLEGAL_ROOT_ELEMENT, E_USER_ERROR);
}
$bindAttr =& $rootNode->getAttributeNode($this->rootBindAttributeName);
$bindValue = $bindAttr->getValue();
if($this->isInitialized) {
if ($bindValue != $this->clientClsName) {
trigger_error(ParamsProxy::FOREIGN_CONFIG_FILE, E_USER_ERROR);
}
} else {
if ($bindValue != $this->ownClassName) {
trigger_error(ParamsProxy::FOREIGN_CONFIG_FILE, E_USER_ERROR);
}
}
return $rootNode;
}
private function parseParamNode (&$paramNode) {
$nodeData = array ();
if (!$paramNode->hasChildNodes()) {
trigger_error(ParamsProxy::EMPTY_PARAM_ELEMENT, E_USER_ERROR);
}
$count = $paramNode->childCount;
if ($count < 2) {
trigger_error(ParamsProxy::TOO_FEW_PROPERTIES, E_USER_ERROR);
} elseif ($count > 3) {
trigger_error(ParamsProxy::TOO_MANY_PROPERTIES, E_USER_ERROR);
}
$children = $paramNode->childNodes;
$hasName = false;
for ($i=0; $i<$count; $i++) {
$child =& $children[$i];
if ($child->nodeType == $this->propertyElementType) {
$name = $child->nodeName;
if ($name === $this->propertyElementName_name ||
$name === $this->propertyElementName_type ||
$name === $this->propertyElementName_value) {
$value = $child->getText();
$value = trim($value);
if ($name === $this->propertyElementName_type) {
if ($value !== $this->propertyElementValue_string &&
$value !== $this->propertyElementValue_number &&
$value !== $this->propertyElementValue_boolean &&
$value !== $this->propertyElementValue_array &&
$value !== $this->propertyElementValue_null &&
$value !== $this->propertyElementValue_auto) {
trigger_error(ParamsProxy::INVALID_PROPERTY_NAME, E_USER_ERROR);
}
}
if ($name === $this->propertyElementName_name) {
$hasName = true;
}
$nodeData [$name] = $value;
} else {
trigger_error(ParamsProxy::ILLEGAL_PROPERTY, E_USER_ERROR);
}
}
}
if (!$hasName) {
trigger_error(ParamsProxy::MISSING_REQUIRED_NAME, E_USER_ERROR);
}
if ($nodeData[$this->propertyElementName_type] == $this->propertyElementValue_null) {
$nodeData[$this->propertyElementName_value] = null;
}
if ($nodeData[$this->propertyElementName_type] == null) {
$nodeData[$this->propertyElementName_type] = $this->propertyElementValue_auto;
}
if ($nodeData[$this->propertyElementName_type] == $this->propertyElementValue_auto) {
$value = $nodeData[$this->propertyElementName_value];
if ($value === "null") {
$nodeData[$this->propertyElementName_value] = null;
} elseif ($value === "true") {
$nodeData[$this->propertyElementName_value] = true;
} elseif ($value === "false") {
$nodeData[$this->propertyElementName_value] = false;
} elseif (is_numeric ($value)) {
$isNegative = $value[0] == "-";
$hasDecimalPart = (stripos($value,".") !== false);
$hasExponentialPart = (stripos($value,"e") !== false);
$converted = 0;
if ($hasDecimalPart || $hasExponentialPart) {
$converted = (float) $value;
} else {
$converted = (int) $value;
}
if ($isNegative && $converted > 0) {
$converted *= -1;
}
if ("" . $converted === $value) {
$nodeData[$this->propertyElementName_value] = $converted;
}
}
} elseif ($nodeData[$this->propertyElementName_type] ==
$this->propertyElementValue_boolean) {
$value = $nodeData[$this->propertyElementName_value];
if ($value === "true") {
$nodeData[$this->propertyElementName_value] = true;
} elseif ($value === "false") {
$nodeData[$this->propertyElementName_value] = false;
} else {
trigger_error(ParamsProxy::ILLEGAL_STRING_FOR_BOOLEAN_DEFINITION, E_USER_ERROR);
}
} elseif ($nodeData[$this->propertyElementName_type] ==
$this->propertyElementValue_number) {
$value = $nodeData[$this->propertyElementName_value];
if (is_numeric ($value)) {
$isNegative = $value[0] == "-";
$hasDecimalPart = (stripos($value,".") !== false);
$hasExponentialPart = (stripos($value,"e") !== false);
$converted = 0;
if ($hasDecimalPart || $hasExponentialPart) {
$converted = (float) $value;
} else {
$converted = (int) $value;
}
if ($isNegative && $converted > 0) {
$converted *= -1;
}
$nodeData[$this->propertyElementName_value] = $converted;
} else {
trigger_error(ParamsProxy::ILLEGAL_STRING_FOR_NUMBER_DEFINITION, E_USER_ERROR);
}
} elseif ($nodeData[$this->propertyElementName_type] ==
$this->propertyElementValue_array) {
$nodeData[$this->propertyElementName_value] = $this->extractArrayValue ($paramNode);
}
if (!@isset ($nodeData[$this->propertyElementName_value]) &&
!@is_null ($nodeData[$this->propertyElementName_value])) {
trigger_error(ParamsProxy::MISSING_REQUIRED_VALUE, E_USER_ERROR);
} elseif ($nodeData[$this->propertyElementName_value] === "") {
trigger_error(ParamsProxy::MISSING_REQUIRED_VALUE, E_USER_ERROR);
}
return $nodeData;
}
private function extractArrayValue (&$paramNode) {
$ret = array();
$nodeList =& $paramNode->getElementsByTagName($this->propertyElementName_value);
$valueEl = $nodeList->item(0);
$count = $valueEl->childCount;
$children = $valueEl->childNodes;
for ($i=0; $i<$count; $i++) {
$itemData = array();
$child =& $children[$i];
if ($child->nodeType == $this->propertyElementType) {
if ($child->nodeName != $this->propertyElementName_item) {
trigger_error(ParamsProxy::ILLEGAL_ITEM_ELEMENT, E_USER_ERROR);
}
$attributes =& $child->attributes;
$numAttributes =& $attributes->getLength();
if ($numAttributes > 2) {
trigger_error(ParamsProxy::TOO_MANY_ITEM_ATTRIBUTES, E_USER_ERROR);
}
for ($j=0; $j<$numAttributes; $j++) {
$attribute =& $attributes->item($j);
$attrName = $attribute->getName();
$attrValue = $attribute->getValue();
if ($attrName == $this->attributeName_key ||
$attrName == $this->attributeName_type) {
if ($attrValue == "") {
trigger_error(ParamsProxy::ILLEGAL_ITEM_ELEMENT_ATTRIBUTE_VALUE,
E_USER_ERROR);
}
if ($attrName == $this->attributeName_key) {
if (is_numeric($attrValue)) {
$converted = (int) $attrValue;
if ("" . $converted === $attrValue) {
$attrValue = $converted;
}
}
$itemData[$this->attributeName_key] = $attrValue;
}
if ($attrName == $this->attributeName_type){
if ($attrValue == $this->attributeValue_string ||
$attrValue == $this->attributeValue_number ||
$attrValue == $this->attributeValue_boolean ||
$attrValue == $this->attributeValue_null ||
$attrValue == $this->attributeValue_auto) {
$itemData[$this->attributeName_type] = $attrValue;
} else {
trigger_error(ParamsProxy::ILLEGAL_ITEM_ELEMENT_ATTRIBUTE_VALUE,
E_USER_ERROR);
}
}
} else {
trigger_error(ParamsProxy::ILLEGAL_ITEM_ELEMENT_ATTRIBUTE_NAME,
E_USER_ERROR);
}
}
$itemValue = $child->getText();
$itemValue = trim ($itemValue);
$itemData["value"] = $itemValue;
if (!isset ($itemData[$this->attributeName_type])) {
$itemData[$this->attributeName_type] = $this->attributeValue_auto;
}
if ($itemData[$this->attributeName_type] == $this->attributeValue_null) {
$itemData["value"] = null;
}
if ($itemData[$this->attributeName_type] !== $this->attributeValue_null &&
$itemData["value"] === "") {
trigger_error(ParamsProxy::EMPTY_ITEM_ELEMENT, E_USER_ERROR);
}
switch ($itemData[$this->attributeName_type]) {
case $this->attributeValue_number:
if (is_numeric($itemData["value"])) {
$isNegative = ($itemData["value"][0] == "-");
$hasDecimalPart = (stripos($itemData["value"],".") !== false);
$hasExponentialPart = (stripos($itemData["value"],"e") !== false);
$converted = 0;
if ($hasDecimalPart || $hasExponentialPart) {
$converted = (float) $itemData["value"];
} else {
$converted = (int) $itemData["value"];
}
if ($isNegative && $converted > 0) {
$converted *= -1;
}
$itemData["value"] = $converted;
} else {
trigger_error(ParamsProxy::ILLEGAL_STRING_FOR_NUMBER_DEFINITION,
E_USER_ERROR);
}
break;
case $this->attributeValue_boolean:
if ($itemData["value"] === "true") {
$itemData["value"] = true;
} elseif ($itemData["value"] === "false") {
$itemData["value"] = false;
} else {
trigger_error(ParamsProxy::ILLEGAL_STRING_FOR_BOOLEAN_DEFINITION,
E_USER_ERROR);
}
break;
case $this->attributeValue_auto:
if ($itemData["value"] === "null") {
$itemData["value"] = null;
} elseif ($itemData["value"] === "true") {
$itemData["value"] = true;
} elseif ($itemData["value"] === "true") {
$itemData["value"] = false;
} elseif (is_numeric ($itemData["value"])) {
$value = $itemData["value"];
$isNegative = $value[0] == "-";
$hasDecimalPart = (stripos($value,".") !== false);
$hasExponentialPart = (stripos($value,"e") !== false);
$converted = 0;
if ($hasDecimalPart || $hasExponentialPart) {
$converted = (float) $value;
} else {
$converted = (int) $value;
}
if ($isNegative && $converted > 0) {
$converted *= -1;
}
if ("" . $converted === $value) {
$itemData["value"] = $converted;
}
}
break;
}
if (isset ($itemData[$this->attributeName_key])) {
$ret [$itemData[$this->attributeName_key]] = $itemData["value"];
} else {
$ret[] = $itemData["value"];
}
}
}
return $ret;
}
}
?>