/library/tools/trm/php/firePHP/Insight/Encoder/Default.php

https://code.google.com/p/haquery/ · PHP · 738 lines · 592 code · 105 blank · 41 comment · 141 complexity · 029fd5f213eeaa9922409aadb2c4ee36 MD5 · raw file

  1. <?php
  2. class Insight_Encoder_Default {
  3. const UNDEFINED = '_U_N_D_E_F_I_N_E_D_';
  4. const SKIP = '_S_K_I_P_';
  5. protected $options = array('depthNoLimit' => false,
  6. 'lengthNoLimit' => false,
  7. 'maxDepth' => 5,
  8. 'maxArrayDepth' => 3,
  9. 'maxArrayLength' => 25,
  10. 'maxObjectDepth' => 3,
  11. 'maxObjectLength' => 25,
  12. 'maxStringLength' => 5000,
  13. 'rootDepth' => 0,
  14. 'exception.traceOffset' => 0,
  15. 'exception.traceMaxLength' => -1,
  16. 'trace.maxLength' => -1,
  17. 'includeLanguageMeta' => true,
  18. 'treatArrayMapAsDictionary' => false);
  19. /**
  20. * @insight filter = on
  21. */
  22. protected $_origin = self::UNDEFINED;
  23. /**
  24. * @insight filter = on
  25. */
  26. protected $_meta = null;
  27. /**
  28. * @insight filter = on
  29. */
  30. protected $_instances = array();
  31. public function setOption($name, $value)
  32. {
  33. $this->options[$name] = $value;
  34. }
  35. public function setOptions($options)
  36. {
  37. if(count($diff = array_diff(array_keys($options), array_keys($this->options)))>0) {
  38. throw new Exception('Unknown options: ' . implode(',', $diff));
  39. }
  40. $this->options = Insight_Util::array_merge($this->options, $options);
  41. }
  42. public function setOrigin($variable)
  43. {
  44. $this->_origin = $variable;
  45. // reset some variables
  46. $this->_instances = array();
  47. return true;
  48. }
  49. public function setMeta($meta)
  50. {
  51. $this->_meta = $meta;
  52. }
  53. public function getOption($name) {
  54. // check for option in meta first, then fall back to default options
  55. if(isset($this->_meta['encoder.' . $name])) {
  56. return $this->_meta['encoder.' . $name];
  57. } else
  58. if(isset($this->options[$name])) {
  59. return $this->options[$name];
  60. }
  61. return null;
  62. }
  63. public function encode($data=self::UNDEFINED, $meta=self::UNDEFINED)
  64. {
  65. if($data!==self::UNDEFINED) {
  66. $this->setOrigin($data);
  67. }
  68. if($meta!==self::UNDEFINED) {
  69. $this->setMeta($meta);
  70. }
  71. $graph = array();
  72. if($this->_origin!==self::UNDEFINED) {
  73. $graph['origin'] = $this->_encodeVariable($this->_origin);
  74. }
  75. if($this->_instances) {
  76. foreach( $this->_instances as $key => $value ) {
  77. $graph['instances'][$key] = $value[1];
  78. }
  79. }
  80. if($this->getOption('includeLanguageMeta')) {
  81. if(!$this->_meta) {
  82. $this->_meta = array();
  83. }
  84. if(!isset($this->_meta['lang.id'])) {
  85. $this->_meta['lang.id'] = 'registry.pinf.org/cadorn.org/github/renderers/packages/php/master';
  86. }
  87. }
  88. // remove encoder options
  89. $meta = array();
  90. foreach( $this->_meta as $name => $value ) {
  91. if(!($name=="encoder" || substr($name, 0, 8)=="encoder.")) {
  92. $meta[$name] = $value;
  93. }
  94. }
  95. return array(Insight_Util::json_encode($graph), ($meta)?$meta:false);
  96. }
  97. protected function _encodeVariable($Variable, $ObjectDepth = 1, $ArrayDepth = 1, $MaxDepth = 1)
  98. {
  99. /*
  100. if($Variable===self::UNDEFINED) {
  101. $var = array('type'=>'constant', 'constant'=>'undefined');
  102. if($this->options['includeLanguageMeta']) {
  103. $var['lang.type'] = 'undefined';
  104. }
  105. return $var;
  106. } else
  107. */
  108. if(is_null($Variable)) {
  109. $var = array('type'=>'constant', 'constant'=>'null');
  110. if($this->getOption('includeLanguageMeta')) {
  111. $var['lang.type'] = 'null';
  112. }
  113. } else
  114. if(is_bool($Variable)) {
  115. $var = array('type'=>'constant', 'constant'=>($Variable)?'true':'false');
  116. if($this->getOption('includeLanguageMeta')) {
  117. $var['lang.type'] = 'boolean';
  118. }
  119. } else
  120. if(is_int($Variable)) {
  121. $var = array('type'=>'text', 'text'=>(string)$Variable);
  122. if($this->getOption('includeLanguageMeta')) {
  123. $var['lang.type'] = 'integer';
  124. }
  125. } else
  126. if(is_float($Variable)) {
  127. $var = array('type'=>'text', 'text'=>(string)$Variable);
  128. if($this->getOption('includeLanguageMeta')) {
  129. $var['lang.type'] = 'float';
  130. }
  131. } else
  132. if(is_object($Variable)) {
  133. $sub = $this->_encodeInstance($Variable, $ObjectDepth, $ArrayDepth, $MaxDepth);
  134. $var = array('type'=>'reference', 'reference'=> $sub['value']);
  135. if(isset($sub['meta'])) {
  136. $var = Insight_Util::array_merge($var, $sub['meta']);
  137. }
  138. } else
  139. if(is_array($Variable)) {
  140. if(isset($Variable[self::SKIP])) {
  141. unset($Variable[self::SKIP]);
  142. return $Variable;
  143. }
  144. $sub = null;
  145. // Check if we have an indexed array (list) or an associative array (map)
  146. if(Insight_Util::is_list($Variable)) {
  147. $sub = $this->_encodeArray($Variable, $ObjectDepth, $ArrayDepth, $MaxDepth);
  148. $var = array('type'=>'array', 'array'=> $sub['value']);
  149. } else
  150. if($this->getOption('treatArrayMapAsDictionary')) {
  151. $sub = $this->_encodeAssociativeArray($Variable, $ObjectDepth, $ArrayDepth, $MaxDepth);
  152. $var = array('type'=>'dictionary', 'dictionary'=> isset($sub['value'])?$sub['value']:false);
  153. } else {
  154. $sub = $this->_encodeAssociativeArray($Variable, $ObjectDepth, $ArrayDepth, $MaxDepth);
  155. $var = array('type'=>'map', 'map'=> isset($sub['value'])?$sub['value']:false);
  156. }
  157. if(isset($sub['meta'])) {
  158. $var = Insight_Util::array_merge($var, $sub['meta']);
  159. }
  160. if($this->getOption('includeLanguageMeta')) {
  161. $var['lang.type'] = 'array';
  162. }
  163. } else
  164. if(is_resource($Variable)) {
  165. // TODO: Try and get more info about resource
  166. $var = array('type'=>'text', 'text'=>(string)$Variable);
  167. if($this->getOption('includeLanguageMeta')) {
  168. $var['lang.type'] = 'resource';
  169. }
  170. } else
  171. if(is_string($Variable)) {
  172. $var = array('type'=>'text');
  173. // TODO: Add info about encoding
  174. if(Insight_Util::is_utf8($Variable)) {
  175. $var['text'] = $Variable;
  176. } else {
  177. $var['text'] = utf8_encode($Variable);
  178. }
  179. $maxLength = $this->getOption('maxStringLength');
  180. $lengthNoLimit = $this->getOption('lengthNoLimit');
  181. if($maxLength>=0 && strlen($var['text'])>=$maxLength && $lengthNoLimit!==true) {
  182. $var['encoder.trimmed'] = true;
  183. $var['encoder.trimmed.partial'] = true;
  184. $var['encoder.notice'] = 'Max String Length ('.$maxLength.') ' . (strlen($var['text'])-$maxLength) . ' more';
  185. $var['text'] = substr($var['text'], 0, $maxLength);
  186. }
  187. if($this->getOption('includeLanguageMeta')) {
  188. $var['lang.type'] = 'string';
  189. }
  190. } else {
  191. $var = array('type'=>'text', 'text'=>(string)$Variable);
  192. if($this->getOption('includeLanguageMeta')) {
  193. $var['lang.type'] = 'unknown';
  194. }
  195. }
  196. return $var;
  197. }
  198. protected function _isObjectMemberFiltered($ClassName, $MemberName) {
  199. $filter = $this->getOption('filter');
  200. if(!isset($filter['classes']) || !is_array($filter['classes'])) {
  201. return false;
  202. }
  203. if(!isset($filter['classes'][$ClassName]) || !is_array($filter['classes'][$ClassName])) {
  204. return false;
  205. }
  206. return in_array($MemberName, $filter['classes'][$ClassName]);
  207. }
  208. protected function _getInstanceID($Object)
  209. {
  210. foreach( $this->_instances as $key => $instance ) {
  211. if($instance[0]===$Object) {
  212. return $key;
  213. }
  214. }
  215. return null;
  216. }
  217. protected function _encodeInstance($Object, $ObjectDepth = 1, $ArrayDepth = 1, $MaxDepth = 1)
  218. {
  219. if(($ret=$this->_checkDepth('Object', $ObjectDepth, $MaxDepth))!==false) {
  220. return $ret;
  221. }
  222. $id = $this->_getInstanceID($Object);
  223. if($id!==null) {
  224. return array('value'=>$id);
  225. }
  226. $id = sizeof($this->_instances);
  227. $this->_instances[$id] = array($Object);
  228. $this->_instances[$id][1] = $this->_encodeObject($Object, $ObjectDepth, $ArrayDepth, $MaxDepth);
  229. return array('value'=>$id);
  230. }
  231. protected function _checkDepth($Type, $TypeDepth, $MaxDepth) {
  232. $depthNoLimit = $this->getOption('depthNoLimit');
  233. if($depthNoLimit===true) {
  234. return false;
  235. }
  236. $rootDepth = $this->getOption('rootDepth');
  237. // If we are traversing to $rootDepth we ignore or adjust max and type depth
  238. if($rootDepth>0) {
  239. if($MaxDepth == $TypeDepth) {
  240. if($MaxDepth <= $rootDepth) {
  241. return false;
  242. }
  243. $TypeDepth -= $rootDepth;
  244. }
  245. $MaxDepth -= $rootDepth;
  246. }
  247. $maxDepthOption = $this->getOption('maxDepth');
  248. // NOTE: Not sure why >= is needed here (shout just be > as below but then it does not match up)
  249. if ($maxDepthOption>=0 && $MaxDepth >= $maxDepthOption) {
  250. return array(
  251. 'value' => null,
  252. 'meta' => array(
  253. 'encoder.trimmed' => true,
  254. 'encoder.notice' => 'Max Depth ('.$this->getOption('maxDepth').')'
  255. )
  256. );
  257. }
  258. $maxDepthOption = $this->getOption('max' . $Type . 'Depth');
  259. if ($maxDepthOption>=0 && $TypeDepth > $maxDepthOption) {
  260. return array(
  261. 'value' => null,
  262. 'meta' => array(
  263. 'encoder.trimmed' => true,
  264. 'encoder.notice' => 'Max ' . $Type . ' Depth ('.$this->getOption('max' . $Type . 'Depth').')'
  265. )
  266. );
  267. }
  268. return false;
  269. }
  270. protected function _encodeAssociativeArray($Variable, $ObjectDepth = 1, $ArrayDepth = 1, $MaxDepth = 1)
  271. {
  272. if(($ret=$this->_checkDepth('Array', $ArrayDepth, $MaxDepth))!==false) {
  273. return $ret;
  274. }
  275. $index = 0;
  276. $maxLength = $this->getOption('maxArrayLength');
  277. $lengthNoLimit = $this->getOption('lengthNoLimit');
  278. $isGlobals = false;
  279. foreach ($Variable as $key => $val) {
  280. // Encoding the $GLOBALS PHP array causes an infinite loop
  281. // if the recursion is not reset here as it contains
  282. // a reference to itself. This is the only way I have come up
  283. // with to stop infinite recursion in this case.
  284. if($key=='GLOBALS'
  285. && is_array($val)
  286. && array_key_exists('GLOBALS',$val)) {
  287. $isGlobals = true;
  288. }
  289. if($isGlobals) {
  290. switch($key) {
  291. case 'GLOBALS':
  292. $val = array(
  293. self::SKIP => true,
  294. 'encoder.trimmed' => true,
  295. 'encoder.notice' => 'Recursion (GLOBALS)'
  296. );
  297. break;
  298. case '_ENV':
  299. case 'HTTP_ENV_VARS':
  300. case '_POST':
  301. case 'HTTP_POST_VARS':
  302. case '_GET':
  303. case 'HTTP_GET_VARS':
  304. case '_COOKIE':
  305. case 'HTTP_COOKIE_VARS':
  306. case '_SERVER':
  307. case 'HTTP_SERVER_VARS':
  308. case '_FILES':
  309. case 'HTTP_POST_FILES':
  310. case '_REQUEST':
  311. $val = array(
  312. self::SKIP => true,
  313. 'encoder.trimmed' => true,
  314. 'encoder.notice' => 'Automatically Excluded (GLOBALS)'
  315. );
  316. break;
  317. }
  318. }
  319. if($this->getOption('treatArrayMapAsDictionary')) {
  320. if(!Insight_Util::is_utf8($key)) {
  321. $key = utf8_encode($key);
  322. }
  323. $return[$key] = $this->_encodeVariable($val, 1, $ArrayDepth + 1);
  324. } else {
  325. $return[] = array($this->_encodeVariable($key), $this->_encodeVariable($val, 1, $ArrayDepth + 1, $MaxDepth + 1));
  326. }
  327. $index++;
  328. if($maxLength>=0 && $index>=$maxLength && $lengthNoLimit!==true) {
  329. if($this->getOption('treatArrayMapAsDictionary')) {
  330. $return['...'] = array(
  331. 'encoder.trimmed' => true,
  332. 'encoder.notice' => 'Max Array Length ('.$maxLength.') ' . (count($Variable)-$maxLength) . ' more'
  333. );
  334. } else {
  335. $return[] = array(array(
  336. 'encoder.trimmed' => true,
  337. 'encoder.notice' => 'Max Array Length ('.$maxLength.') ' . (count($Variable)-$maxLength) . ' more'
  338. ), array(
  339. 'encoder.trimmed' => true,
  340. 'encoder.notice' => 'Max Array Length ('.$maxLength.') ' . (count($Variable)-$maxLength) . ' more'
  341. ));
  342. }
  343. break;
  344. }
  345. }
  346. return array('value'=>$return);
  347. }
  348. protected function _encodeArray($Variable, $ObjectDepth = 1, $ArrayDepth = 1, $MaxDepth = 1)
  349. {
  350. if(($ret=$this->_checkDepth('Array', $ArrayDepth, $MaxDepth))!==false) {
  351. return $ret;
  352. }
  353. $items = array();
  354. $index = 0;
  355. $maxLength = $this->getOption('maxArrayLength');
  356. $lengthNoLimit = $this->getOption('lengthNoLimit');
  357. foreach ($Variable as $val) {
  358. $items[] = $this->_encodeVariable($val, 1, $ArrayDepth + 1, $MaxDepth + 1);
  359. $index++;
  360. if($maxLength>=0 && $index>=$maxLength && $lengthNoLimit!==true) {
  361. $items[] = array(
  362. 'encoder.trimmed' => true,
  363. 'encoder.notice' => 'Max Array Length ('.$maxLength.') ' . (count($Variable)-$maxLength) . ' more'
  364. );
  365. break;
  366. }
  367. }
  368. return array('value'=>$items);
  369. }
  370. protected function _encodeObject($Object, $ObjectDepth = 1, $ArrayDepth = 1, $MaxDepth = 1)
  371. {
  372. $return = array('type'=>'dictionary', 'dictionary'=>array());
  373. $class = get_class($Object);
  374. if($this->getOption('includeLanguageMeta')) {
  375. if($Object instanceof Exception) {
  376. $return['lang.type'] = 'exception';
  377. } else {
  378. $return['lang.type'] = 'object';
  379. }
  380. $return['lang.class'] = $class;
  381. }
  382. $classAnnotations = $this->_getClassAnnotations($class);
  383. $properties = $this->_getClassProperties($class);
  384. $reflectionClass = new ReflectionClass($class);
  385. if($this->getOption('includeLanguageMeta')) {
  386. $return['lang.file'] = $reflectionClass->getFileName();
  387. }
  388. $maxLength = $this->getOption('maxObjectLength');
  389. $lengthNoLimit = $this->getOption('lengthNoLimit');
  390. $maxLengthReached = false;
  391. $members = (array)$Object;
  392. foreach( $properties as $name => $property ) {
  393. if($name=='__insight_tpl_id') {
  394. $return['tpl.id'] = $property->getValue($Object);
  395. continue;
  396. }
  397. if($maxLength>=0 && count($return['dictionary'])>$maxLength && $lengthNoLimit!==true) {
  398. $maxLengthReached = true;
  399. break;
  400. }
  401. $info = array();
  402. $info['name'] = $name;
  403. $raw_name = $name;
  404. if($property->isStatic()) {
  405. $info['static'] = 1;
  406. }
  407. if($property->isPublic()) {
  408. $info['visibility'] = 'public';
  409. } else
  410. if($property->isPrivate()) {
  411. $info['visibility'] = 'private';
  412. $raw_name = "\0".$class."\0".$raw_name;
  413. } else
  414. if($property->isProtected()) {
  415. $info['visibility'] = 'protected';
  416. $raw_name = "\0".'*'."\0".$raw_name;
  417. }
  418. if(isset($classAnnotations['$'.$name])
  419. && isset($classAnnotations['$'.$name]['filter'])
  420. && $classAnnotations['$'.$name]['filter']=='on') {
  421. $info['notice'] = 'Trimmed by annotation filter';
  422. } else
  423. if($this->_isObjectMemberFiltered($class, $name)) {
  424. $info['notice'] = 'Trimmed by registered filters';
  425. }
  426. if(method_exists($property, 'setAccessible')) {
  427. $property->setAccessible(true);
  428. }
  429. if(isset($info['notice'])) {
  430. $info['trimmed'] = true;
  431. try {
  432. $info['value'] = $this->_trimVariable($property->getValue($Object));
  433. } catch(ReflectionException $e) {
  434. $info['value'] = $this->_trimVariable(self::UNDEFINED);
  435. $info['notice'] .= ', Need PHP 5.3 to get value';
  436. }
  437. } else {
  438. $value = self::UNDEFINED;
  439. if(array_key_exists($raw_name,$members)) {
  440. // if(array_key_exists($raw_name,$members)
  441. // && !$property->isStatic()) {
  442. $value = $members[$raw_name];
  443. } else {
  444. try {
  445. $value = $property->getValue($Object);
  446. } catch(ReflectionException $e) {
  447. $info['value'] = $this->_trimVariable(self::UNDEFINED);
  448. $info['notice'] = 'Need PHP 5.3 to get value';
  449. }
  450. }
  451. if($value!==self::UNDEFINED) {
  452. // NOTE: This is a bit of a hack but it works for now
  453. if($Object instanceof Exception && $name=='trace' && $this->getOption('exception.traceOffset')!==null) {
  454. $offset = $this->getOption('exception.traceOffset');
  455. if($offset==-1) {
  456. array_unshift($value, array(
  457. 'file' => $Object->getFile(),
  458. 'line' => $Object->getLine(),
  459. 'type' => 'throw',
  460. 'class' => $class,
  461. 'args' => array(
  462. $Object->getMessage()
  463. )
  464. ));
  465. } else
  466. if($offset>0) {
  467. array_splice($value, 0, $offset);
  468. }
  469. }
  470. if($Object instanceof Exception && $name=='trace') {
  471. $length = $this->getOption('exception.traceMaxLength');
  472. if($length>0) {
  473. $value = array_slice($value, 0, $length);
  474. }
  475. }
  476. }
  477. if($value!==self::UNDEFINED) {
  478. $info['value'] = $this->_encodeVariable($value, $ObjectDepth + 1, 1, $MaxDepth + 1);
  479. }
  480. }
  481. $return['dictionary'][$info['name']] = $info['value'];
  482. if(isset($info['notice'])) {
  483. $return['dictionary'][$info['name']]['encoder.notice'] = $info['notice'];
  484. }
  485. if(isset($info['trimmed'])) {
  486. $return['dictionary'][$info['name']]['encoder.trimmed'] = $info['trimmed'];
  487. }
  488. if($this->getOption('includeLanguageMeta')) {
  489. if(isset($info['visibility'])) {
  490. $return['dictionary'][$info['name']]['lang.visibility'] = $info['visibility'];
  491. }
  492. if(isset($info['static'])) {
  493. $return['dictionary'][$info['name']]['lang.static'] = $info['static'];
  494. }
  495. }
  496. // $return['members'][] = $info;
  497. }
  498. if(!$maxLengthReached) {
  499. // Include all members that are not defined in the class
  500. // but exist in the object
  501. foreach( $members as $name => $value ) {
  502. if ($name{0} == "\0") {
  503. $parts = explode("\0", $name);
  504. $name = $parts[2];
  505. }
  506. if($maxLength>=0 && count($return['dictionary'])>$maxLength && $lengthNoLimit!==true) {
  507. $maxLengthReached = true;
  508. break;
  509. }
  510. if(!isset($properties[$name])) {
  511. $info = array();
  512. $info['undeclared'] = 1;
  513. $info['name'] = $name;
  514. if(isset($classAnnotations['$'.$name])
  515. && isset($classAnnotations['$'.$name]['filter'])
  516. && $classAnnotations['$'.$name]['filter']=='on') {
  517. $info['notice'] = 'Trimmed by annotation filter';
  518. } else
  519. if($this->_isObjectMemberFiltered($class, $name)) {
  520. $info['notice'] = 'Trimmed by registered filters';
  521. }
  522. if(isset($info['notice'])) {
  523. $info['trimmed'] = true;
  524. $info['value'] = $this->_trimVariable($value);
  525. } else {
  526. $info['value'] = $this->_encodeVariable($value, $ObjectDepth + 1, 1, $MaxDepth + 1);
  527. }
  528. $return['dictionary'][$info['name']] = $info['value'];
  529. if($this->getOption('includeLanguageMeta')) {
  530. $return['dictionary'][$info['name']]['lang.undeclared'] = 1;
  531. }
  532. if(isset($info['notice'])) {
  533. $return['dictionary'][$info['name']]['encoder.notice'] = $info['notice'];
  534. }
  535. if(isset($info['trimmed'])) {
  536. $return['dictionary'][$info['name']]['encoder.trimmed'] = $info['trimmed'];
  537. }
  538. // $return['members'][] = $info;
  539. }
  540. }
  541. }
  542. if($maxLengthReached) {
  543. $keys = array_keys($return['dictionary']);
  544. unset($return['dictionary'][array_pop($keys)]);
  545. $return['dictionary']['...'] = array(
  546. 'encoder.trimmed' => true,
  547. 'encoder.notice' => 'Max Object Length ('.$maxLength.') ' . (count($members)-$maxLength) . ' more'
  548. );
  549. }
  550. return $return;
  551. }
  552. protected function _trimVariable($var, $length=20)
  553. {
  554. if(is_null($var)) {
  555. $text = 'NULL';
  556. } else
  557. if(is_bool($var)) {
  558. $text = ($var)?'TRUE':'FALSE';
  559. } else
  560. if(is_int($var) || is_float($var) || is_double($var)) {
  561. $text = $this->_trimString((string)$var, $length);
  562. } else
  563. if(is_object($var)) {
  564. $text = $this->_trimString(get_class($var), $length);
  565. } else
  566. if(is_array($var)) {
  567. $text = $this->_trimString(serialize($var), $length);
  568. } else
  569. if(is_resource($var)) {
  570. $text = $this->_trimString('' . $var);
  571. } else
  572. if(is_string($var)) {
  573. $text = $this->_trimString($var, $length);
  574. } else {
  575. $text = $this->_trimString($var, $length);
  576. }
  577. return array(
  578. 'type' => 'text',
  579. 'text' => $text
  580. );
  581. }
  582. protected function _trimString($string, $length=20)
  583. {
  584. if(strlen($string)<=$length+3) {
  585. return $string;
  586. }
  587. return substr($string, 0, $length) . '...';
  588. }
  589. protected function _getClassProperties($class)
  590. {
  591. $reflectionClass = new ReflectionClass($class);
  592. $properties = array();
  593. // Get parent properties first
  594. if($parent = $reflectionClass->getParentClass()) {
  595. $properties = $this->_getClassProperties($parent->getName());
  596. }
  597. foreach( $reflectionClass->getProperties() as $property) {
  598. $properties[$property->getName()] = $property;
  599. }
  600. return $properties;
  601. }
  602. protected function _getClassAnnotations($class)
  603. {
  604. $annotations = array();
  605. // TODO: Go up to parent classes (let subclasses override tags from parent classes)
  606. try {
  607. $reflectionClass = new Zend_Reflection_Class($class);
  608. foreach( $reflectionClass->getProperties() as $property ) {
  609. $docblock = $property->getDocComment();
  610. if($docblock) {
  611. $tags = $docblock->getTags('insight');
  612. if($tags) {
  613. foreach($tags as $tag) {
  614. list($name, $value) = $this->_parseAnnotationTag($tag);
  615. $annotations['$'.$property->getName()][$name] = $value;
  616. }
  617. }
  618. }
  619. }
  620. } catch(Exception $e) {
  621. // silence errors (Zend_Reflection_Docblock_Tag throws if '@name(..)' tag found)
  622. // TODO: Optionally show these errors
  623. }
  624. return $annotations;
  625. }
  626. protected function _parseAnnotationTag($tag) {
  627. if(!preg_match_all('/^([^)\s]*?)\s*=\s*(.*?)$/si', $tag->getDescription(), $m)) {
  628. Insight_Annotator::setVariables(array('tag'=>$tag));
  629. throw new Exception('Tag format not valid!');
  630. }
  631. return array($m[1][0], $m[2][0]);
  632. }
  633. }