PageRenderTime 41ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/sgl/includes/qcubed/_core/framework/QType.class.php

http://logisticsouth.googlecode.com/
PHP | 422 lines | 229 code | 43 blank | 150 comment | 46 complexity | d0889637a4588e7fd1d28767733fb7dc MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0
  1. <?php
  2. /**
  3. * The exception that is thrown by QType::Cast
  4. * if an invalid cast is performed. InvalidCastException
  5. * derives from CallerException, and therefore should be handled
  6. * similar to how CallerExceptions are handled (e.g. IncrementOffset should
  7. * be called whenever an InvalidCastException is caught and rethrown).
  8. */
  9. class QInvalidCastException extends QCallerException {
  10. public function __construct($strMessage, $intOffset = 2) {
  11. parent::__construct($strMessage, $intOffset);
  12. }
  13. }
  14. /**
  15. * Type Library to add some support for strongly named types.
  16. *
  17. * PHP does not support strongly named types. The QCubed type library
  18. * and QCubed typing in general attempts to bring some structure to types
  19. * when passing in values, properties, parameters to/from QCubed framework objects
  20. * and methods.
  21. *
  22. * The Type library attempts to allow as much flexibility as possible to
  23. * set and cast variables to other types, similar to how PHP does it natively,
  24. * but simply adds a big more structure to it.
  25. *
  26. * For example, regardless if a variable is an integer, boolean, or string,
  27. * QType::Cast will allow the flexibility of those values to interchange with
  28. * each other with little to no issue.
  29. *
  30. * In addition to value objects (ints, bools, floats, strings), the Type library
  31. * also supports object casting. While technically casting one object to another
  32. * is not a true cast, QType::Cast does at least ensure that the tap being "casted"
  33. * to is a legitamate subclass of the object being "cast". So if you have ParentClass,
  34. * and you have a ChildClass that extends ParentClass,
  35. * $objChildClass = new ChildClass();
  36. * $objParentClass = new ParentClass();
  37. * Type::Cast($objChildClass, 'ParentClass'); // is a legal cast
  38. * Type::Cast($objParentClass, 'ChildClass'); // will throw an InvalidCastException
  39. *
  40. * For values, specifically int to string conversion, one different between
  41. * QType::Cast and PHP (in order to add structure) is that if an integer contains
  42. * alpha characters, PHP would normally allow that through w/o complaint, simply
  43. * ignoring any numeric characters past the first alpha character. QType::Cast
  44. * would instead throw an InvalidCastException to let the developer immedaitely
  45. * know that something doesn't look right.
  46. *
  47. * In theory, the type library should maintain the same level of flexibility
  48. * PHP developers are accostomed to, while providing a mechanism to limit
  49. * careless coding errors and tough to figure out mistakes due to PHP's sometimes
  50. * overly laxed type conversions.
  51. */
  52. abstract class QType {
  53. /**
  54. * This faux constructor method throws a caller exception.
  55. * The Type object should never be instantiated, and this constructor
  56. * override simply guarantees it.
  57. *
  58. * @return void
  59. */
  60. public final function __construct() {
  61. throw new QCallerException('Type should never be instantiated. All methods and variables are publically statically accessible.');
  62. }
  63. const String = 'string';
  64. const Integer = 'integer';
  65. const Float = 'double';
  66. const Boolean = 'boolean';
  67. const Object = 'object';
  68. const ArrayType = 'array';
  69. const DateTime = 'QDateTime';
  70. const Resource = 'resource';
  71. private static function CastObjectTo($objItem, $strType) {
  72. try {
  73. $objReflection = new ReflectionClass($objItem);
  74. if ($objReflection->getName() == 'SimpleXMLElement') {
  75. switch ($strType) {
  76. case QType::String:
  77. return (string) $objItem;
  78. case QType::Integer:
  79. try {
  80. return QType::Cast((string) $objItem, QType::Integer);
  81. } catch (QCallerException $objExc) {
  82. $objExc->IncrementOffset();
  83. throw $objExc;
  84. }
  85. case QType::Boolean:
  86. $strItem = strtolower(trim((string) $objItem));
  87. if (($strItem == 'false') ||
  88. (!$strItem))
  89. return false;
  90. else
  91. return true;
  92. }
  93. }
  94. if ($objItem instanceof $strType)
  95. return $objItem;
  96. } catch (Exception $objExc) {
  97. }
  98. throw new QInvalidCastException(sprintf('Unable to cast %s object to %s', $objReflection->getName(), $strType));
  99. }
  100. private static function CastValueTo($mixItem, $strNewType) {
  101. $strOriginalType = gettype($mixItem);
  102. switch (QType::TypeFromDoc($strNewType)) {
  103. case QType::Boolean:
  104. if ($strOriginalType == QType::Boolean)
  105. return $mixItem;
  106. if (is_null($mixItem))
  107. return false;
  108. if (strlen($mixItem) == 0)
  109. return false;
  110. if (strtolower($mixItem) == 'false')
  111. return false;
  112. settype($mixItem, $strNewType);
  113. return $mixItem;
  114. case QType::Integer:
  115. if($strOriginalType == QType::Boolean)
  116. throw new QInvalidCastException(sprintf('Unable to cast %s value to %s: %s', $strOriginalType, $strNewType, $mixItem));
  117. if (strlen($mixItem) == 0)
  118. return null;
  119. if ($strOriginalType == QType::Integer)
  120. return $mixItem;
  121. // Check to make sure the value hasn't changed significantly
  122. $intItem = $mixItem;
  123. settype($intItem, $strNewType);
  124. $mixTest = $intItem;
  125. settype($mixTest, $strOriginalType);
  126. // If the value hasn't changed, it's safe to return the casted value
  127. if ((string)$mixTest === (string)$mixItem)
  128. return $intItem;
  129. // if casting changed the value, but we have a valid integer, return with a string cast
  130. if (preg_match('/^-?\d+$/',$mixItem) === 1)
  131. return (string)$mixItem;
  132. // any other scenarios is an invalid cast
  133. throw new QInvalidCastException(sprintf('Unable to cast %s value to %s: %s', $strOriginalType, $strNewType, $mixItem));
  134. case QType::Float:
  135. if($strOriginalType == QType::Boolean)
  136. throw new QInvalidCastException(sprintf('Unable to cast %s value to %s: %s', $strOriginalType, $strNewType, $mixItem));
  137. if (strlen($mixItem) == 0)
  138. return null;
  139. if ($strOriginalType == QType::Float)
  140. return $mixItem;
  141. if (!is_numeric($mixItem))
  142. throw new QInvalidCastException(sprintf('Invalid float: %s', $mixItem));
  143. // Check to make sure the value hasn't changed significantly
  144. $fltItem = $mixItem;
  145. settype($fltItem, $strNewType);
  146. $mixTest = $fltItem;
  147. settype($mixTest, $strOriginalType);
  148. //account for any scientific notation that results
  149. //find out what notation is currently being used
  150. $i = strpos($mixItem, '.');
  151. $precision = ($i === false) ? 0 : strlen($mixItem) - $i - 1;
  152. //and represent the casted value the same way
  153. $strTest = sprintf('%.' . $precision . 'f', $fltItem);
  154. // If the value hasn't changed, it's safe to return the casted value
  155. if ((string)$strTest === (string)$mixItem)
  156. return $fltItem;
  157. // the changed value could be the result of loosing precision. Return the original value with no cast
  158. return $mixItem;
  159. case QType::String:
  160. if ($strOriginalType == QType::String)
  161. return $mixItem;
  162. // Check to make sure the value hasn't changed significantly
  163. $strItem = $mixItem;
  164. settype($strItem, $strNewType);
  165. $mixTest = $strItem;
  166. settype($mixTest, $strOriginalType);
  167. // Has it?
  168. $blnSame = true;
  169. if ($strOriginalType == QType::Float) {
  170. // type conversion from float to string affects precision and can throw off the comparison
  171. // so we need to use a comparison check using an epsilon value instead
  172. $epsilon = 1.0e-14;
  173. $diff = abs($mixItem - $mixTest);
  174. if ($diff > $epsilon) {
  175. $blnSame = false;
  176. }
  177. }
  178. else {
  179. if ($mixTest != $mixItem)
  180. $blnSame = false;
  181. }
  182. if (!$blnSame)
  183. //This is an invalid cast
  184. throw new QInvalidCastException(sprintf('Unable to cast %s value to %s: %s', $strOriginalType, $strNewType, $mixItem));
  185. return $strItem;
  186. default:
  187. throw new QInvalidCastException(sprintf('Unable to cast %s value to unknown type %s', $strOriginalType, $strNewType));
  188. }
  189. }
  190. private static function CastArrayTo($arrItem, $strType) {
  191. if ($strType == QType::ArrayType)
  192. return $arrItem;
  193. else
  194. throw new QInvalidCastException(sprintf('Unable to cast Array to %s', $strType));
  195. }
  196. /**
  197. * Used to cast a variable to another type. Allows for moderate
  198. * support of strongly-named types.
  199. *
  200. * Will throw an exception if the cast fails, causes unexpected side effects,
  201. * if attempting to cast an object to a value (or vice versa), or if an object
  202. * is being cast to a class that isn't a subclass (e.g. parent). The exception
  203. * thrown will be an InvalidCastException, which extends CallerException.
  204. *
  205. * @param mixed $mixItem the value, array or object that you want to cast
  206. * @param string $strType the type to cast to. Can be a QType::XXX constant (e.g. QType::Integer), or the name of a Class
  207. * @return mixed the passed in value/array/object that has been cast to strType
  208. */
  209. public final static function Cast($mixItem, $strType) {
  210. // Automatically Return NULLs
  211. if (is_null($mixItem))
  212. return null;
  213. // Figure out what PHP thinks the type is
  214. $strPhpType = gettype($mixItem);
  215. switch ($strPhpType) {
  216. case QType::Object:
  217. try {
  218. return QType::CastObjectTo($mixItem, $strType);
  219. } catch (QCallerException $objExc) {
  220. $objExc->IncrementOffset();
  221. throw $objExc;
  222. }
  223. case QType::String:
  224. case QType::Integer:
  225. case QType::Float:
  226. case QType::Boolean:
  227. try {
  228. return QType::CastValueTo($mixItem, $strType);
  229. } catch (QCallerException $objExc) {
  230. $objExc->IncrementOffset();
  231. throw $objExc;
  232. }
  233. case QType::ArrayType:
  234. try {
  235. return QType::CastArrayTo($mixItem, $strType);
  236. } catch (QCallerException $objExc) {
  237. $objExc->IncrementOffset();
  238. throw $objExc;
  239. }
  240. case QType::Resource:
  241. // Cannot Cast Resources
  242. throw new QInvalidCastException('Resources cannot be cast');
  243. default:
  244. // Could not determine type
  245. throw new QInvalidCastException(sprintf('Unable to determine type of item to be cast: %s', $mixItem));
  246. }
  247. }
  248. /**
  249. * Used by the QCubed Code Generator to allow for the code generation of
  250. * the actual "Type::Xxx" constant, instead of the text of the constant,
  251. * in generated code.
  252. *
  253. * It is rare for Constant to be used manually outside of Code Generation.
  254. *
  255. * @param string $strType the type to convert to 'constant' form
  256. * @return string the text of the Text:Xxx Constant
  257. */
  258. public final static function Constant($strType) {
  259. switch ($strType) {
  260. case QType::Object: return 'QType::Object';
  261. case QType::String: return 'QType::String';
  262. case QType::Integer: return 'QType::Integer';
  263. case QType::Float: return 'QType::Float';
  264. case QType::Boolean: return 'QType::Boolean';
  265. case QType::ArrayType: return 'QType::ArrayType';
  266. case QType::Resource: return 'QType::Resource';
  267. case QType::DateTime: return 'QType::DateTime';
  268. default:
  269. // Could not determine type
  270. throw new QInvalidCastException(sprintf('Unable to determine type of item to lookup its constant: %s', $strType));
  271. }
  272. }
  273. public final static function TypeFromDoc($strType) {
  274. switch (strtolower($strType)) {
  275. case 'string':
  276. case 'str':
  277. return QType::String;
  278. case 'integer':
  279. case 'int':
  280. return QType::Integer;
  281. case 'float':
  282. case 'flt':
  283. case 'double':
  284. case 'dbl':
  285. case 'single':
  286. case 'decimal':
  287. return QType::Float;
  288. case 'bool':
  289. case 'boolean':
  290. case 'bit':
  291. return QType::Boolean;
  292. case 'datetime':
  293. case 'date':
  294. case 'time':
  295. case 'qdatetime':
  296. return QType::DateTime;
  297. case 'null':
  298. case 'void':
  299. return 'void';
  300. default:
  301. try {
  302. $objReflection = new ReflectionClass($strType);
  303. return $strType;
  304. } catch (ReflectionException $objExc) {
  305. throw new QInvalidCastException(sprintf('Unable to determine type of item from PHPDoc Comment to lookup its QType or Class: %s', $strType));
  306. }
  307. }
  308. }
  309. /**
  310. * Used by the QCubed Code Generator and QSoapService class to allow for the xml generation of
  311. * the actual "s:type" Soap Variable types.
  312. *
  313. * @param string $strType the type to convert to 'constant' form
  314. * @return string the text of the SOAP standard s:type variable type
  315. */
  316. public final static function SoapType($strType) {
  317. switch ($strType) {
  318. case QType::String: return 'string';
  319. case QType::Integer: return 'int';
  320. case QType::Float: return 'float';
  321. case QType::Boolean: return 'boolean';
  322. case QType::DateTime: return 'dateTime';
  323. case QType::ArrayType:
  324. case QType::Object:
  325. case QType::Resource:
  326. default:
  327. // Could not determine type
  328. throw new QInvalidCastException(sprintf('Unable to determine type of item to lookup its constant: %s', $strType));
  329. }
  330. }
  331. /*
  332. final public static function SoapArrayType($strType) {
  333. try {
  334. return sprintf('ArrayOf%s', ucfirst(QType::SoapType($strType)));
  335. } catch (QInvalidCastException $objExc) {}
  336. $objExc->IncrementOffset();
  337. throw $objExc;
  338. }
  339. }
  340. final public static function AlterSoapComplexTypeArray(&$strComplexTypeArray, $strType) {
  341. switch ($strType) {
  342. case QType::String:
  343. $strItemName = 'string';
  344. break;
  345. case QType::Integer:
  346. $strItemName = 'int';
  347. break;
  348. case QType::Float:
  349. $strItemName = 'float';
  350. break;
  351. case QType::Boolean:
  352. $strItemName = 'boolean';
  353. break;
  354. case QType::DateTime:
  355. $strItemName = 'dateTime';
  356. break;
  357. case QType::ArrayType:
  358. case QType::Object:
  359. case QType::Resource:
  360. default:
  361. // Could not determine type
  362. throw new QInvalidCastException(sprintf('Unable to determine type of item to lookup its constant: %s', $strType));
  363. }
  364. $strArrayName = QType::SoapArrayType($strType);
  365. if (!array_key_exists($strArrayName, $strComplexTypeArray))
  366. $strComplexTypeArray[$strArrayName] = sprintf(
  367. '<s:complexType name="%s"><s:sequence>' .
  368. '<s:element minOccurs="0" maxOccurs="unbounded" name="%s" type="%s"/>' .
  369. '</s:sequence></s:complexType>',
  370. QType::SoapArrayType($strType),
  371. $strItemName,
  372. QType::SoapType($strType));
  373. }*/
  374. }
  375. ?>