AmfphpDiscoveryService.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <?php
  2. /**
  3. * This file is part of amfPHP
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the license that is bundled
  8. * with this package in the file license.txt.
  9. */
  10. /**
  11. * analyses existing services. Warning: if 2 or more services have the same name, only one will appear in the returned data,
  12. * as it is an associative array using the service name as key.
  13. * @amfphpHide
  14. * @package Amfphp_Plugins_Discovery
  15. * @author Ariel Sommeria-Klein
  16. */
  17. class AmfphpDiscoveryService {
  18. /**
  19. * @see AmfphpDiscovery
  20. * @var array of strings(patterns)
  21. */
  22. public static $excludePaths;
  23. /**
  24. * paths to folders containing services(relative or absolute). set by plugin.
  25. * @var array of paths
  26. */
  27. public static $serviceFolders;
  28. /**
  29. *
  30. * @var array of ClassFindInfo. set by plugin.
  31. */
  32. public static $serviceNames2ClassFindInfo;
  33. /**
  34. * restrict access to amfphp_admin.
  35. * @var boolean
  36. */
  37. public static $restrictAccess;
  38. /**
  39. * get method roles
  40. * @param string $methodName
  41. * @return array
  42. */
  43. public function _getMethodRoles($methodName) {
  44. if (self::$restrictAccess) {
  45. return array('amfphp_admin');
  46. }
  47. }
  48. /**
  49. * finds classes in folder. If in subfolders add the relative path to the name.
  50. * recursive, so use with care.
  51. * @param string $rootPath
  52. * @param string $subFolder
  53. * @return array
  54. */
  55. protected function searchFolderForServices($rootPath, $subFolder) {
  56. $ret = array();
  57. $folderContent = scandir($rootPath . $subFolder);
  58. if ($folderContent) {
  59. foreach ($folderContent as $fileName) {
  60. //add all .php file names, but removing the .php suffix
  61. if (strpos($fileName, '.php')) {
  62. $fullServiceName = $subFolder . substr($fileName, 0, strlen($fileName) - 4);
  63. $ret[] = $fullServiceName;
  64. } else if ((substr($fileName, 0, 1) != '.') && is_dir($rootPath . $subFolder . $fileName)) {
  65. $ret = array_merge($ret, $this->searchFolderForServices($rootPath, $subFolder . $fileName . '/'));
  66. }
  67. }
  68. }
  69. return $ret;
  70. }
  71. /**
  72. * returns a list of available services
  73. * @param array $serviceFolders
  74. * @param array $serviceNames2ClassFindInfo
  75. * @return array of service names
  76. */
  77. protected function getServiceNames(array $serviceFolders, array $serviceNames2ClassFindInfo) {
  78. $ret = array();
  79. foreach ($serviceFolders as $serviceFolderPath) {
  80. if(is_array($serviceFolderPath)){
  81. //case when using namespace
  82. $ret = array_merge($ret, $this->searchFolderForServices($serviceFolderPath[0], ''));
  83. }else{
  84. //case without namespace
  85. $ret = array_merge($ret, $this->searchFolderForServices($serviceFolderPath, ''));
  86. }
  87. }
  88. foreach ($serviceNames2ClassFindInfo as $key => $value) {
  89. $ret[] = $key;
  90. }
  91. return $ret;
  92. }
  93. /**
  94. * extracts
  95. * - meta data from param tags:
  96. * 1) type is first word after tag name, name of the variable is second word ($ is removed)
  97. * 2) example is end of line after 'example: '
  98. *
  99. * - return type
  100. * If data is missing because comment is incomplete the values are simply not set
  101. * @param string $comment
  102. * @return array{'returns' => type, 'params' => array{variable name => parameter meta}}
  103. */
  104. protected function parseMethodComment($comment) {
  105. $exploded = explode('@', $comment);
  106. $ret = array();
  107. $params = array();
  108. foreach ($exploded as $tagLine) {
  109. if (strtolower(substr($tagLine, 0, 5)) == 'param') {
  110. //type
  111. $words = explode(' ', $tagLine);
  112. $type = trim($words[1]);
  113. $varName = trim(str_replace('$', '', $words[2]));
  114. $paramMeta = array();
  115. $paramMeta['type'] = $type;
  116. //example
  117. $example = '';
  118. $examplePos = strpos($tagLine, 'example:');
  119. if($examplePos !== false){
  120. $example = substr($tagLine, $examplePos + 8);
  121. }
  122. $paramMeta['example'] = $example;
  123. $params[$varName] = $paramMeta;
  124. } else if (strtolower(substr($tagLine, 0, 6)) == 'return') {
  125. $words = explode(' ', $tagLine);
  126. $type = trim($words[1]);
  127. $ret['return'] = $type;
  128. }
  129. }
  130. $ret['param'] = $params;
  131. if (!isset($ret['return'])) {
  132. $ret['return'] = '';
  133. }
  134. return $ret;
  135. }
  136. /**
  137. * gets rid of blocks of 4 spaces and tabs, as well as comment markers.
  138. * @param type $comment
  139. * @return type
  140. */
  141. private function formatComment($comment){
  142. $ret = str_replace(' ', '', $comment);
  143. $ret = str_replace("\t", '', $ret);
  144. $ret = str_replace('/**', '', $ret);
  145. $ret = str_replace('*/', '', $ret);
  146. $ret = str_replace('*', '', $ret);
  147. return $ret;
  148. }
  149. /**
  150. * does the actual collection of data about available services
  151. * @return array of AmfphpDiscovery_ServiceInfo
  152. */
  153. public function discover() {
  154. $serviceNames = $this->getServiceNames(self::$serviceFolders, self::$serviceNames2ClassFindInfo);
  155. $ret = array();
  156. foreach ($serviceNames as $serviceName) {
  157. $serviceObject = Amfphp_Core_Common_ServiceRouter::getServiceObjectStatically($serviceName, self::$serviceFolders, self::$serviceNames2ClassFindInfo);
  158. $objR = new ReflectionObject($serviceObject);
  159. $objComment = $this->formatComment($objR->getDocComment());
  160. if (false !== strpos($objComment, '@amfphpHide')) {
  161. //methods including @amfHide should not appear in the back office but should still be accessible.
  162. continue;
  163. }
  164. $methodRs = $objR->getMethods(ReflectionMethod::IS_PUBLIC);
  165. $methods = array();
  166. foreach ($methodRs as $methodR) {
  167. $methodName = $methodR->name;
  168. if (substr($methodName, 0, 1) == '_') {
  169. //methods starting with a '_' as they are reserved, so filter them out
  170. continue;
  171. }
  172. $parameters = array();
  173. $paramRs = $methodR->getParameters();
  174. $methodComment = $this->formatComment($methodR->getDocComment());
  175. if (false !== strpos($methodComment, '@amfphpHide')) {
  176. //methods including @amfHide should not appear in the back office but should still be accessible.
  177. continue;
  178. }
  179. $parsedMethodComment = $this->parseMethodComment($methodComment);
  180. foreach ($paramRs as $paramR) {
  181. $parameterName = $paramR->name;
  182. //get type from type hinting or from parsed method comment. type hinting has priority
  183. $type = '';
  184. //get example from parsed method comment only
  185. $example = '';
  186. if (isset($parsedMethodComment['param'][$parameterName])) {
  187. $paramMeta = $parsedMethodComment['param'][$parameterName];
  188. if(isset($paramMeta['type'])){
  189. $type = $paramMeta['type'];
  190. }
  191. if(isset($paramMeta['example'])){
  192. $example = $paramMeta['example'];
  193. }
  194. }
  195. try{
  196. //this code will throw an exception saying that the class does not exist, only if the class is a namespace.
  197. //in that case there's not much that can be done, so just ignore type.
  198. if ($paramR->getClass()) {
  199. $type = $paramR->getClass()->name;
  200. }
  201. }catch(Exception $e){
  202. }
  203. $parameterInfo = new AmfphpDiscovery_ParameterDescriptor($parameterName, $type, $example);
  204. $parameters[] = $parameterInfo;
  205. }
  206. //get return from parsed return comment if exists
  207. $return = '';
  208. if(isset ($parsedMethodComment['return'])){
  209. $return = $parsedMethodComment['return'];
  210. }
  211. $methods[$methodName] = new AmfphpDiscovery_MethodDescriptor($methodName, $parameters, $methodComment, $return);
  212. }
  213. $ret[$serviceName] = new AmfphpDiscovery_ServiceDescriptor($serviceName, $methods, $objComment);
  214. }
  215. //note : filtering must be done at the end, as for example excluding a Vo class needed by another creates issues
  216. foreach ($ret as $serviceName => $serviceObj) {
  217. foreach (self::$excludePaths as $excludePath) {
  218. if (strpos($serviceName, $excludePath) !== false) {
  219. unset($ret[$serviceName]);
  220. break;
  221. }
  222. }
  223. }
  224. return $ret;
  225. }
  226. }
  227. ?>