PageRenderTime 43ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/phocoa/framework/WFObject.php

https://github.com/SwissalpS/phocoa
PHP | 768 lines | 657 code | 34 blank | 77 comment | 51 complexity | fc97c8072c8417786cc5dd8380990321 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. /**
  4. * @package framework-base
  5. * @copyright Copyright (c) 2005 Alan Pinstein. All Rights Reserved.
  6. * @version $Id: kvcoding.php,v 1.3 2004/12/12 02:44:09 alanpinstein Exp $
  7. * @author Alan Pinstein <apinstein@mac.com>
  8. */
  9. /**
  10. * Base Class for all framework classes.
  11. *
  12. * Provides:
  13. * - {@link KeyValueCoding}
  14. */
  15. class WFObject implements WFKeyValueCoding
  16. {
  17. function __construct()
  18. {
  19. }
  20. /**
  21. * Break circular references so this object will GC pre-5.2.
  22. *
  23. * NOTE: if the value of the var is an array, the array will be iterated on as well and the destroy() method called (if available) and the array references removed.
  24. *
  25. * @param array A list of all instances to destroy.
  26. */
  27. private $alreadyInDestroy = false;
  28. public function destroy($vars = array())
  29. {
  30. if ($this->alreadyInDestroy) return;
  31. $this->alreadyInDestroy = true;
  32. foreach ($vars as $v) {
  33. $toBeDestroyed = $this->$v;
  34. if ($toBeDestroyed)
  35. {
  36. $_destroyedClass = get_class($toBeDestroyed);
  37. $o = memory_get_usage();
  38. if (is_object($toBeDestroyed) and method_exists($toBeDestroyed, 'destroy'))
  39. {
  40. $toBeDestroyed->destroy();
  41. }
  42. else if (is_array($toBeDestroyed))
  43. {
  44. while ($tbd = array_pop($toBeDestroyed)) { // destroys more memory than foreach() here for some reason
  45. if (method_exists($tbd, 'destroy'))
  46. {
  47. $tbd->destroy();
  48. }
  49. else
  50. {
  51. // for debugging this will tell you what doesn't have destroy impemented
  52. //print "==&gt; No destroy for " . get_class($this) . "::\${$v}[x]<br>\n";
  53. }
  54. }
  55. }
  56. else
  57. {
  58. // for debugging this will tell you what doesn't have destroy impemented
  59. //print "==&gt; No destroy for " . get_class($this) . "::\${$v}<br>\n";
  60. }
  61. $this->$v = NULL;
  62. //print "Recovered " . ($o - memory_get_usage()) . " by destroying " . get_class($this) . "::\${$v} [{$_destroyedClass}]<br>\n";
  63. }
  64. }
  65. }
  66. /**
  67. * Empty placeholder for exposedProperties setup.
  68. *
  69. * Subclasses should call parent and merge results.
  70. *
  71. * @return array
  72. */
  73. public static function exposedProperties()
  74. {
  75. return array();
  76. }
  77. protected static function _valueForStaticKey($key, $target)
  78. {
  79. if ($key == NULL) throw( new WFUndefinedKeyException("NULL key Exception") );
  80. $performed = false;
  81. // try calling getter with naming convention "key"
  82. if (method_exists($target, $key))
  83. {
  84. $result = call_user_func(array($target, $key));
  85. $performed = true;
  86. }
  87. // try calling getter with naming convention "getKey"
  88. if (!$performed)
  89. {
  90. $getterMethod = 'get' . ucfirst($key);
  91. if (method_exists($target, $getterMethod))
  92. {
  93. $result = call_user_func(array($target, $getterMethod));
  94. $performed = true;
  95. }
  96. }
  97. // try accessing property directly
  98. if (!$performed)
  99. {
  100. $vars = get_class_vars($target);
  101. if (array_key_exists($key, $vars))
  102. {
  103. throw( new WFException("No way to support this before PHP 5.3.") );
  104. //$result = $target::$$key;
  105. $performed = true;
  106. }
  107. }
  108. if (!$performed)
  109. {
  110. $result = self::valueForUndefinedStaticKey($key);
  111. }
  112. return $result;
  113. }
  114. function valueForKey($key)
  115. {
  116. if ($key == NULL) throw( new WFUndefinedKeyException("NULL key Exception") );
  117. $performed = false;
  118. // try calling getter with naming convention "key"
  119. if (method_exists($this, $key))
  120. {
  121. $result = $this->$key();
  122. $performed = true;
  123. }
  124. // try calling getter with naming convention "getKey"
  125. if (!$performed)
  126. {
  127. $getterMethod = 'get' . ucfirst($key);
  128. if (method_exists($this, $getterMethod))
  129. {
  130. $result = $this->$getterMethod();
  131. $performed = true;
  132. }
  133. }
  134. // try accessing property directly
  135. if (!$performed)
  136. {
  137. $vars = get_object_vars($this);
  138. if (array_key_exists($key, $vars))
  139. {
  140. $result = $this->$key;
  141. $performed = true;
  142. }
  143. }
  144. if (!$performed)
  145. {
  146. $result = $this->valueForUndefinedKey($key);
  147. }
  148. return $result;
  149. }
  150. function valuesForKeys($keys)
  151. {
  152. if (!is_array($keys)) throw new WFException("valuesForKeys() requires an array as first argument.");
  153. $hash = array();
  154. foreach ($keys as $k) {
  155. $v = $this->valueForKey($k);
  156. $hash[$k] = $v;
  157. }
  158. return $hash;
  159. }
  160. function setValuesForKeys($valuesForKeys)
  161. {
  162. foreach ($valuesForKeys as $k => $v) {
  163. $this->setValueForKey($v, $k);
  164. }
  165. return $this;
  166. }
  167. function setValuesForKeyPaths($valuesForKeyPaths)
  168. {
  169. foreach ($valuesForKeyPaths as $k => $v) {
  170. $this->setValueForKeyPath($v, $k);
  171. }
  172. return $this;
  173. }
  174. function valuesForKeyPaths($keysAndKeyPaths)
  175. {
  176. $hash = array();
  177. // fix integer keys into keys... this allows keysAndKeyPaths to return ('myProp', 'myProp2' => 'myKeyPath', 'myProp3')
  178. foreach ( array_keys($keysAndKeyPaths) as $k ) {
  179. if (gettype($k) == 'integer')
  180. {
  181. $keysAndKeyPaths[$keysAndKeyPaths[$k]] = $keysAndKeyPaths[$k];
  182. unset($keysAndKeyPaths[$k]);
  183. }
  184. }
  185. foreach ($keysAndKeyPaths as $k => $keyPath) {
  186. $v = $this->valueForKeyPath($keyPath);
  187. $hash[$k] = $v;
  188. }
  189. return $hash;
  190. }
  191. /**
  192. * Called by valueForKey() if the key cannot be located through normal methods.
  193. *
  194. * The default implementation raises as WFUndefinedKeyException. Subclasses can override this function to return an alternate value for the undefined key.
  195. * @param string The key.
  196. * @return mixed The value of the key.
  197. * @throws object WFUndefinedKeyException
  198. */
  199. function valueForUndefinedKey($key)
  200. {
  201. throw( new WFUndefinedKeyException("Unknown key '$key' (" . gettype($key) . ") requested for object '" . get_class($this) . "'.") );
  202. }
  203. /**
  204. * Called by valueForStaticKey() if the key cannot be located through normal methods.
  205. *
  206. * The default implementation raises as WFUndefinedKeyException. Subclasses can override this function to return an alternate value for the undefined key.
  207. * @param string The key.
  208. * @return mixed The value of the key.
  209. * @throws object WFUndefinedKeyException
  210. */
  211. public static function valueForUndefinedStaticKey($key)
  212. {
  213. throw( new WFUndefinedKeyException("Unknown key '$key' requested for object '" . __CLASS__ . "'.") );
  214. }
  215. /**
  216. * Helper function for implementing KVC.
  217. *
  218. * Supports "coalescing" KVC by using ; separated keyPaths. The first non-null value returned will be used.
  219. * The *last* keypath is actually the "default default" which is used if all keyPaths return NULL.
  220. *
  221. * This is public so that other objects that don't subclass WFObject can leverage this codebase to implement KVC.
  222. *
  223. * @param string The keyPath.
  224. * @param object Generic The root object to run the keyPath search against.
  225. * @return mixed
  226. * @throws Exception, WFUndefinedKeyException
  227. */
  228. public static function valueForTargetAndKeyPath($inKeyPath, $rootObject = NULL)
  229. {
  230. // detect coalescing keypath
  231. if (strpos($inKeyPath, ';') !== false)
  232. {
  233. $coalescingKeyPaths = preg_split('/(?<!\\\\);/', $inKeyPath);
  234. if (count($coalescingKeyPaths) < 2) throw new WFException("Error parsing coalescing keypath: {$inKeyPath}");
  235. $coalesceDefault = str_replace('\\;', ';', array_pop($coalescingKeyPaths));
  236. $val = NULL;
  237. while ($val === NULL && ($keyPath = array_shift($coalescingKeyPaths))) {
  238. $val = self::valueForTargetAndKeyPathSingle($keyPath, $rootObject);
  239. }
  240. if ($val === NULL)
  241. {
  242. $val = $coalesceDefault;
  243. }
  244. return $val;
  245. }
  246. else
  247. {
  248. return self::valueForTargetAndKeyPathSingle($inKeyPath, $rootObject);
  249. }
  250. }
  251. private static function valueForTargetAndKeyPathSingle($keyPath, $rootObject = NULL)
  252. {
  253. if ($keyPath == NULL) throw( new Exception("NULL keyPath Exception") );
  254. $staticMode = ($rootObject === NULL);
  255. // initialize
  256. $result = NULL;
  257. $keyParts = explode('.', $keyPath);
  258. $keyPartCount = count($keyParts);
  259. // walk keypath
  260. $keys = explode('.', $keyPath);
  261. $keyPartsLeft = $keyCount = count($keys);
  262. $arrayMode = false;
  263. for ($keyI = 0; $keyI < $keyCount; $keyI++) {
  264. $key = $keys[$keyI];
  265. $keyPartsLeft--;
  266. // look for escape hatch
  267. $escapeHatch = false;
  268. $lastChrModifier = substr($key, -1);
  269. if ($lastChrModifier === '^')
  270. {
  271. $escapeHatch = true;
  272. $key = substr($key, 0, strlen($key)-1);
  273. }
  274. // parse out decorate magic, if any
  275. $decoratorClass = NULL;
  276. $decoratorPos = strpos($key, '[');
  277. if ($decoratorPos !== false and substr($key, -1) === ']')
  278. {
  279. $decoratorClass = substr($key, $decoratorPos + 1, -1);
  280. $key = substr($key, 0, $decoratorPos);
  281. }
  282. // determine target; use this if on first key, use result otherwise
  283. if ($keyI == 0)
  284. {
  285. // having "::" in your first key or a rootObject of NULL triggers STATIC mode
  286. if ($staticMode && strpos($key, '::') !== false)
  287. {
  288. $staticParts = explode('::', $key);
  289. if (count($staticParts) !== 2)
  290. {
  291. throw( new WFException("First part of keypath for static KVC must be 'ClassName::StaticMethodName'; you passed: " . $key) );
  292. }
  293. $target = $staticParts[0];
  294. $key = $staticParts[1];
  295. if (!class_exists($target)) throw( new WFException("First part of a static keypath must be a valid class name, you passed: " . $target) );
  296. }
  297. else
  298. {
  299. $target = $rootObject;
  300. }
  301. }
  302. else
  303. {
  304. $target = $result;
  305. }
  306. // get result of this part of path
  307. if ($staticMode && $keyI == 0)
  308. {
  309. if (!is_string($target)) throw( new WFException('Target is not class name at static keyPath: ' . join('.', array_slice($keys, 0, $keyI))) );
  310. $staticF = array($target, '_valueForStaticKey');
  311. if (!is_callable($staticF)) throw( new WFException('Target class (' . $target . ') does not implement WFObject protocol.') );
  312. $result = call_user_func($staticF, $key, $target);
  313. }
  314. else
  315. {
  316. if (!is_object($target)) throw( new WFUndefinedKeyException('Value at keyPath: "' . join('.', array_slice($keys, 0, $keyI)) . "\" is not an object when trying to get next key \"{$key}\".") );
  317. if (!($target instanceof WFObject) and !method_exists($target, 'valueForKey'))
  318. {
  319. throw( new WFException("Target not an object at keypath: " . $keyPath . " for object " . get_class($rootObject)) );
  320. }
  321. $result = $target->valueForKey($key);
  322. }
  323. if (is_array($result) or ($result instanceof ArrayObject))
  324. {
  325. $arrayMode = true;
  326. }
  327. else
  328. {
  329. if ($escapeHatch and $result === NULL and $keyPartsLeft) return NULL;
  330. if ($decoratorClass)
  331. {
  332. if (!class_exists($decoratorClass)) throw new WFException("Decorator {$decoratorClass} does not exist.");
  333. $result = new $decoratorClass($result);
  334. }
  335. }
  336. // IF the result of the a key is an array, we do some magic.
  337. // We CREATE a new array with the results of calling EACH object in the array with the rest of the path.
  338. // We also support several operators: http://developer.apple.com/documentation/Cocoa/Conceptual/KeyValueCoding/Concepts/ArrayOperators.html
  339. if ($arrayMode and $keyPartsLeft)
  340. {
  341. $nextPart = $keys[$keyI + 1];
  342. // are we in operator mode as well?
  343. if (in_array($nextPart, array('@count', '@first', '@firstNotNull', '@sum', '@max', '@min', '@avg', '@unionOfArrays', '@unionOfObjects', '@distinctUnionOfArrays', '@distinctUnionOfObjects')))
  344. {
  345. $operator = $nextPart;
  346. $rightKeyPath = join('.', array_slice($keyParts, $keyI + 2));
  347. }
  348. else
  349. {
  350. $operator = NULL;
  351. $rightKeyPath = join('.', array_slice($keyParts, $keyI + 1));
  352. }
  353. //print "magic on $keyPath at " . join('.', array_slice($keyParts, 0, $keyI + 1)) . " kp: $rightKeyPath\n";
  354. // if there is a rightKeyPath, need to calculate magic array from remaining keypath. Otherwise, just use current result (it's arrayMode) as magicArray.
  355. if ($rightKeyPath)
  356. {
  357. $magicArray = array();
  358. foreach ($result as $object) {
  359. if (!is_object($object)) throw( new Exception("All array items must be OBJECTS THAT IMPLEMENT Key-Value Coding for KVC Magic Arrays to work.") );
  360. if (!method_exists($object, 'valueForKey')) throw( new Exception("target is not Key-Value Coding compliant for valueForKey.") );
  361. if ($decoratorClass)
  362. {
  363. if (!class_exists($decoratorClass)) throw new WFException("Decorator {$decoratorClass} does not exist.");
  364. $object = new $decoratorClass($object);
  365. }
  366. $magicArray[] = $object->valueForKeyPath($rightKeyPath);
  367. }
  368. }
  369. else
  370. {
  371. $magicArray = $result;
  372. }
  373. if ($operator)
  374. {
  375. switch ($operator) {
  376. case '@count':
  377. $result = count($magicArray);
  378. break;
  379. case '@first':
  380. if (count($magicArray) > 0)
  381. {
  382. $result = current($magicArray);
  383. }
  384. else
  385. {
  386. $result = null;
  387. }
  388. break;
  389. case '@firstNotNull':
  390. $result = null;
  391. foreach ($magicArray as $v) {
  392. if ($v !== NULL)
  393. {
  394. $result = $v;
  395. break;
  396. }
  397. }
  398. break;
  399. case '@sum':
  400. $result = array_sum ( $magicArray );
  401. break;
  402. case '@max':
  403. $result = max ( $magicArray );
  404. break;
  405. case '@min':
  406. $result = min ( $magicArray );
  407. break;
  408. case '@avg':
  409. $result = array_sum ( $magicArray ) / count($magicArray);
  410. break;
  411. case '@unionOfArrays':
  412. $result = array();
  413. foreach ($magicArray as $item) {
  414. if (!is_array($item))
  415. {
  416. throw( new Exception("unionOfArrays requires that all results be arrays... non-array encountered: $item") );
  417. }
  418. $result = array_merge($item, $result);
  419. }
  420. break;
  421. // I think this is equivalent to what our magic arrays do anyway
  422. // for instance: transactions.payee will give a list of all payee objects of each transaction
  423. // it would seem: transactions.@unionOfObjects.payee would yield the same?
  424. case '@unionOfObjects':
  425. $result = $magicArray;
  426. break;
  427. case '@distinctUnionOfArrays':
  428. $result = array();
  429. foreach ($magicArray as $item) {
  430. if (!is_array($item))
  431. {
  432. throw( new Exception("distinctUnionOfArrays requires that all results be arrays... non-array encountered: $item") );
  433. }
  434. $result = array_merge($item, $result);
  435. }
  436. $result = array_unique($result);
  437. break;
  438. case '@distinctUnionOfObjects':
  439. $result = array_unique($magicArray);
  440. break;
  441. }
  442. }
  443. else
  444. {
  445. $result = $magicArray;
  446. }
  447. break;
  448. }
  449. }
  450. return $result;
  451. }
  452. public function valueForKeyPath($keyPath)
  453. {
  454. return self::valueForTargetAndKeyPath($keyPath, $this);
  455. }
  456. public static function valueForStaticKeyPath($keyPath)
  457. {
  458. return self::valueForTargetAndKeyPath($keyPath);
  459. }
  460. public static function valueForStaticKey($key)
  461. {
  462. return self::valueForStaticKeyPath($key);
  463. }
  464. /**
  465. * Returns the current object.
  466. *
  467. * Useful for KVC "hacking" in cases where you need to use KVC magic on the "current" object.
  468. * Examples: this[MyDecorator].name, this.@first (for an array), etc.
  469. *
  470. * @return object WFObject
  471. */
  472. public function this()
  473. {
  474. return $this;
  475. }
  476. function setValueForKey($value, $key)
  477. {
  478. $performed = false;
  479. // try calling setter
  480. $setMethod = "set" . ucfirst($key);
  481. if (method_exists($this, $setMethod))
  482. {
  483. $this->$setMethod($value);
  484. $performed = true;
  485. }
  486. if (!$performed)
  487. {
  488. // try accesing instance var directly
  489. $vars = get_object_vars($this);
  490. if (array_key_exists($key, $vars))
  491. {
  492. $this->$key = $value;
  493. $performed = true;
  494. }
  495. }
  496. if (!$performed)
  497. {
  498. throw( new WFUndefinedKeyException("Unknown key '$key' (" . gettype($key) . ") requested for object '" . get_class($this) . "'.") );
  499. }
  500. return $this;
  501. }
  502. /**
  503. * Helper function to convert a keyPath into the targetKeyPath (the object to call xxxKey on) and the targetKey (the key to call on the target object).
  504. *
  505. * Usage:
  506. *
  507. * <code>
  508. * list($targetKeyPath, $targetKey) = keyPathToParts($keyPath);
  509. * </code>
  510. *
  511. * @return array targetKeyPath, targetKey.
  512. */
  513. protected function keyPathToTargetAndKey($keyPath)
  514. {
  515. if ($keyPath == NULL) throw( new Exception("NULL key Exception") );
  516. // walk keypath
  517. // If the keypath is a.b.c.d then the targetKeyPath is a.b.c and the targetKey is d
  518. $keyParts = explode('.', $keyPath);
  519. $keyPartCount = count($keyParts);
  520. if ($keyPartCount == 0) throw( new Exception("Illegal keyPath: '{$keyPath}'. KeyPath must have at least one part.") );
  521. if ($keyPartCount == 1)
  522. {
  523. $targetKey = $keyPath;
  524. $target = $this;
  525. }
  526. else
  527. {
  528. $targetKey = $keyParts[$keyPartCount - 1];
  529. $targetKeyPath = join('.', array_slice($keyParts, 0, $keyPartCount - 1));
  530. $target = $this->valueForKeyPath($targetKeyPath);
  531. }
  532. return array($target, $targetKey);
  533. }
  534. function setValueForKeyPath($value, $keyPath)
  535. {
  536. list($target, $targetKey) = $this->keyPathToTargetAndKey($keyPath);
  537. if (!is_object($target)) throw( new WFUndefinedKeyException("setValueForKey: target for keypath \"{$keyPath}\" is not an object.") );
  538. return $target->setValueForKey($value, $targetKey);
  539. }
  540. /**
  541. * Validate the given value for the given keypath.
  542. *
  543. * This is the default implementation for this method. It looks for the target object based on the keyPath and then calls the validateValueForKey method.
  544. *
  545. * @param mixed A reference to value to check. Passed by reference so that the implementation can normalize the data.
  546. * @param string keyPath the keyPath for the value.
  547. * @param boolean A reference to a boolean. This value will always be FALSE when the method is called. If the implementation edits the $value, set to TRUE. This will resultion in setValueForKey() being called again with the new value.
  548. * @param object A WFError object describing the error.
  549. * @return boolean TRUE indicates a valid value, FALSE indicates an error.
  550. */
  551. function validateValueForKeyPath(&$value, $keyPath, &$edited, &$errors)
  552. {
  553. list($target, $targetKey) = $this->keyPathToTargetAndKey($keyPath);
  554. if (!($target instanceof WFObject))
  555. {
  556. throw( new WFException("Target not an object at keypath: " . $keyPath . " for object " . get_class($this)) );
  557. }
  558. return $target->validateValueForKey($value, $targetKey, $edited, $errors);
  559. }
  560. /**
  561. * Validate, and call setter if valid, a value for a key.
  562. *
  563. * This is the default implementation for this method. It simply calls validateValueForKey and if there are no errors, calls the setter.
  564. *
  565. * @see validateValueForKey()
  566. */
  567. function validatedSetValueForKey(&$value, $key, &$edited, &$errors)
  568. {
  569. if ($this->validateValueForKey($value, $key, $edited, $errors))
  570. {
  571. $this->setValueForKey($value, $key);
  572. return true;
  573. }
  574. return false;
  575. }
  576. /**
  577. * Default implementation for validateObject().
  578. *
  579. * The default implementation will call all defined Key-Value Validators (any method matching "^validate*") using {@link validatedSetValueForKey()}.
  580. *
  581. * Validations are done via {@link validatedSetValueForKey()}, meaning that changes made to values by the validators will be updated via setValueForKey.
  582. *
  583. * Subclasses needing to do interproperty validation should override the validateObject() method. If subclasses wish to block the default behavior of re-validating
  584. * all properties with validators, then the subclass should not call the super method. Subclasses wishing to preserve this behavior should call parent::validateObject($errors).
  585. *
  586. * @experimental
  587. * @param array An array, passed by reference, which will be populated with any errors encountered. Errors are grouped by key, ie $errors['key'] = array()
  588. * @return boolean TRUE if valid; FALSE if not.
  589. * @throws object WFExecption
  590. * @see WFKeyValueCoding::validateObject()
  591. */
  592. function validateObject(&$errors)
  593. {
  594. if ($errors === null)
  595. {
  596. $errors = new WFErrorArray();
  597. }
  598. $allMethods = get_class_methods(get_class($this));
  599. foreach ($allMethods as $f) {
  600. if (strncasecmp('validate', $f, 8) === 0)
  601. {
  602. // now, make sure it's a KVV method by reflecting the args; should be 3 args.
  603. $methodInfo = new ReflectionMethod(get_class($this), $f);
  604. if ($methodInfo->getNumberOfParameters() !== 3) continue;
  605. $p = $methodInfo->getParameters();
  606. if (!($p[0]->isPassedByReference() and $p[1]->isPassedByReference() and $p[2]->isPassedByReference())) continue;
  607. // we found a real validator! now, validate the value.
  608. $key = strtolower(substr($f, 8, 1)) . substr($f, 9);
  609. $keyErrors = array();
  610. $val = $this->valueForKey($key);
  611. $ok = $this->validatedSetValueForKey($val, $key, $edited, $keyErrors);
  612. if (!$ok)
  613. {
  614. $errors[$key] = $keyErrors;
  615. }
  616. }
  617. }
  618. return count($errors) == 0;
  619. }
  620. /**
  621. * Validate, and call setter if valid, a value for a keyPath.
  622. *
  623. * This is the default implementation for this method. It simply calls validateValueForKeyPath and if there are no errors, calls the setter.
  624. *
  625. * @see validateValueForKeyPath()
  626. */
  627. function validatedSetValueForKeyPath(&$value, $keyPath, &$edited, &$errors)
  628. {
  629. if ($this->validateValueForKeyPath($value, $keyPath, $edited, $errors))
  630. {
  631. $this->setValueForKeyPath($value, $keyPath);
  632. return true;
  633. }
  634. return false;
  635. }
  636. /**
  637. * Validate the given value for the given key.
  638. *
  639. * Clients can normalize the value, and also report and error if the value is not valid.
  640. *
  641. * If the value is valid without modificiation, return TRUE and do not alter $edited or $error.
  642. * If the value is valid after being modified, return TRUE, and $edited to true.
  643. * IF the value is not valid, do not alter $value or $edited, but fill out the $error object with desired information.
  644. *
  645. * The default implementation (in WFObject) looks for a method named validate<key> and calls it, otherwise it returns TRUE. Here is the prototype:
  646. * <code>
  647. * (boolean) function(&$value, &$edited, &$errors)
  648. * </code>
  649. *
  650. * @param mixed A reference to value to check. Passed by reference so that the implementation can normalize the data.
  651. * @param string keyPath the keyPath for the value.
  652. * @param boolean A reference to a boolean. This value will always be FALSE when the method is called. If the implementation edits the $value, set to TRUE.
  653. * @param array An array of WFError objects describing the error. The array is empty by default; you can add new error entries with:
  654. * <code>
  655. * $error[] = new WFError('My error message'); // could also add an error code (string) parameter.
  656. * </code>
  657. * @return boolean TRUE indicates a valid value, FALSE indicates an error.
  658. */
  659. function validateValueForKey(&$value, $key, &$edited, &$errors)
  660. {
  661. $valid = true;
  662. // try calling validator
  663. $validatorMethod = 'validate' . ucfirst($key);
  664. if (method_exists($this, $validatorMethod))
  665. {
  666. // track whether or not validator lies
  667. $errCount = count($errors);
  668. // run validator
  669. $valid = $this->$validatorMethod($value, $edited, $errors);
  670. // check for mismatch b/w $valid and errors generated
  671. $errCount = count($errors) - $errCount;
  672. if ($valid && $errCount) throw( new WFException("Validator for key '{$key}' returned TRUE but also returned errors.") );
  673. else if (!$valid && $errCount === 0) throw( new WFException("Validator for key '{$key}' returned FALSE but didn't provide any errors.") );
  674. }
  675. if (!$valid)
  676. {
  677. WFLog::log("Errors: " . print_r($errors, true), WFLog::TRACE_LOG);
  678. }
  679. return $valid;
  680. }
  681. /* Print a description of the object.
  682. *
  683. * @return string A text description of the object.
  684. */
  685. function __toString()
  686. {
  687. return print_r($this, true);
  688. }
  689. /**
  690. * @todo refactor to getPhpClass() or something. this collides horribly with WFWidget...
  691. * @deprecated
  692. */
  693. function getClass()
  694. {
  695. return get_class($this);
  696. }
  697. }