Brak opisu

ReflectingCommand.php 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <?php
  2. /*
  3. * This file is part of Psy Shell.
  4. *
  5. * (c) 2012-2018 Justin Hileman
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Psy\Command;
  11. use Psy\CodeCleaner\NoReturnValue;
  12. use Psy\Context;
  13. use Psy\ContextAware;
  14. use Psy\Exception\ErrorException;
  15. use Psy\Exception\RuntimeException;
  16. use Psy\Util\Mirror;
  17. /**
  18. * An abstract command with helpers for inspecting the current context.
  19. */
  20. abstract class ReflectingCommand extends Command implements ContextAware
  21. {
  22. const CLASS_OR_FUNC = '/^[\\\\\w]+$/';
  23. const CLASS_MEMBER = '/^([\\\\\w]+)::(\w+)$/';
  24. const CLASS_STATIC = '/^([\\\\\w]+)::\$(\w+)$/';
  25. const INSTANCE_MEMBER = '/^(\$\w+)(::|->)(\w+)$/';
  26. /**
  27. * Context instance (for ContextAware interface).
  28. *
  29. * @var Context
  30. */
  31. protected $context;
  32. /**
  33. * ContextAware interface.
  34. *
  35. * @param Context $context
  36. */
  37. public function setContext(Context $context)
  38. {
  39. $this->context = $context;
  40. }
  41. /**
  42. * Get the target for a value.
  43. *
  44. * @throws \InvalidArgumentException when the value specified can't be resolved
  45. *
  46. * @param string $valueName Function, class, variable, constant, method or property name
  47. *
  48. * @return array (class or instance name, member name, kind)
  49. */
  50. protected function getTarget($valueName)
  51. {
  52. $valueName = \trim($valueName);
  53. $matches = [];
  54. switch (true) {
  55. case \preg_match(self::CLASS_OR_FUNC, $valueName, $matches):
  56. return [$this->resolveName($matches[0], true), null, 0];
  57. case \preg_match(self::CLASS_MEMBER, $valueName, $matches):
  58. return [$this->resolveName($matches[1]), $matches[2], Mirror::CONSTANT | Mirror::METHOD];
  59. case \preg_match(self::CLASS_STATIC, $valueName, $matches):
  60. return [$this->resolveName($matches[1]), $matches[2], Mirror::STATIC_PROPERTY | Mirror::PROPERTY];
  61. case \preg_match(self::INSTANCE_MEMBER, $valueName, $matches):
  62. if ($matches[2] === '->') {
  63. $kind = Mirror::METHOD | Mirror::PROPERTY;
  64. } else {
  65. $kind = Mirror::CONSTANT | Mirror::METHOD;
  66. }
  67. return [$this->resolveObject($matches[1]), $matches[3], $kind];
  68. default:
  69. return [$this->resolveObject($valueName), null, 0];
  70. }
  71. }
  72. /**
  73. * Resolve a class or function name (with the current shell namespace).
  74. *
  75. * @throws ErrorException when `self` or `static` is used in a non-class scope
  76. *
  77. * @param string $name
  78. * @param bool $includeFunctions (default: false)
  79. *
  80. * @return string
  81. */
  82. protected function resolveName($name, $includeFunctions = false)
  83. {
  84. $shell = $this->getApplication();
  85. // While not *technically* 100% accurate, let's treat `self` and `static` as equivalent.
  86. if (\in_array(\strtolower($name), ['self', 'static'])) {
  87. if ($boundClass = $shell->getBoundClass()) {
  88. return $boundClass;
  89. }
  90. if ($boundObject = $shell->getBoundObject()) {
  91. return \get_class($boundObject);
  92. }
  93. $msg = \sprintf('Cannot use "%s" when no class scope is active', \strtolower($name));
  94. throw new ErrorException($msg, 0, E_USER_ERROR, "eval()'d code", 1);
  95. }
  96. if (\substr($name, 0, 1) === '\\') {
  97. return $name;
  98. }
  99. if ($namespace = $shell->getNamespace()) {
  100. $fullName = $namespace . '\\' . $name;
  101. if (\class_exists($fullName) || \interface_exists($fullName) || ($includeFunctions && \function_exists($fullName))) {
  102. return $fullName;
  103. }
  104. }
  105. return $name;
  106. }
  107. /**
  108. * Get a Reflector and documentation for a function, class or instance, constant, method or property.
  109. *
  110. * @param string $valueName Function, class, variable, constant, method or property name
  111. *
  112. * @return array (value, Reflector)
  113. */
  114. protected function getTargetAndReflector($valueName)
  115. {
  116. list($value, $member, $kind) = $this->getTarget($valueName);
  117. return [$value, Mirror::get($value, $member, $kind)];
  118. }
  119. /**
  120. * Resolve code to a value in the current scope.
  121. *
  122. * @throws RuntimeException when the code does not return a value in the current scope
  123. *
  124. * @param string $code
  125. *
  126. * @return mixed Variable value
  127. */
  128. protected function resolveCode($code)
  129. {
  130. try {
  131. $value = $this->getApplication()->execute($code, true);
  132. } catch (\Exception $e) {
  133. // Swallow all exceptions?
  134. }
  135. if (!isset($value) || $value instanceof NoReturnValue) {
  136. throw new RuntimeException('Unknown target: ' . $code);
  137. }
  138. return $value;
  139. }
  140. /**
  141. * Resolve code to an object in the current scope.
  142. *
  143. * @throws RuntimeException when the code resolves to a non-object value
  144. *
  145. * @param string $code
  146. *
  147. * @return object Variable instance
  148. */
  149. private function resolveObject($code)
  150. {
  151. $value = $this->resolveCode($code);
  152. if (!\is_object($value)) {
  153. throw new RuntimeException('Unable to inspect a non-object');
  154. }
  155. return $value;
  156. }
  157. /**
  158. * @deprecated Use `resolveCode` instead
  159. *
  160. * @param string $name
  161. *
  162. * @return mixed Variable instance
  163. */
  164. protected function resolveInstance($name)
  165. {
  166. @\trigger_error('`resolveInstance` is deprecated; use `resolveCode` instead.', E_USER_DEPRECATED);
  167. return $this->resolveCode($name);
  168. }
  169. /**
  170. * Get a variable from the current shell scope.
  171. *
  172. * @param string $name
  173. *
  174. * @return mixed
  175. */
  176. protected function getScopeVariable($name)
  177. {
  178. return $this->context->get($name);
  179. }
  180. /**
  181. * Get all scope variables from the current shell scope.
  182. *
  183. * @return array
  184. */
  185. protected function getScopeVariables()
  186. {
  187. return $this->context->getAll();
  188. }
  189. /**
  190. * Given a Reflector instance, set command-scope variables in the shell
  191. * execution context. This is used to inject magic $__class, $__method and
  192. * $__file variables (as well as a handful of others).
  193. *
  194. * @param \Reflector $reflector
  195. */
  196. protected function setCommandScopeVariables(\Reflector $reflector)
  197. {
  198. $vars = [];
  199. switch (\get_class($reflector)) {
  200. case 'ReflectionClass':
  201. case 'ReflectionObject':
  202. $vars['__class'] = $reflector->name;
  203. if ($reflector->inNamespace()) {
  204. $vars['__namespace'] = $reflector->getNamespaceName();
  205. }
  206. break;
  207. case 'ReflectionMethod':
  208. $vars['__method'] = \sprintf('%s::%s', $reflector->class, $reflector->name);
  209. $vars['__class'] = $reflector->class;
  210. $classReflector = $reflector->getDeclaringClass();
  211. if ($classReflector->inNamespace()) {
  212. $vars['__namespace'] = $classReflector->getNamespaceName();
  213. }
  214. break;
  215. case 'ReflectionFunction':
  216. $vars['__function'] = $reflector->name;
  217. if ($reflector->inNamespace()) {
  218. $vars['__namespace'] = $reflector->getNamespaceName();
  219. }
  220. break;
  221. case 'ReflectionGenerator':
  222. $funcReflector = $reflector->getFunction();
  223. $vars['__function'] = $funcReflector->name;
  224. if ($funcReflector->inNamespace()) {
  225. $vars['__namespace'] = $funcReflector->getNamespaceName();
  226. }
  227. if ($fileName = $reflector->getExecutingFile()) {
  228. $vars['__file'] = $fileName;
  229. $vars['__line'] = $reflector->getExecutingLine();
  230. $vars['__dir'] = \dirname($fileName);
  231. }
  232. break;
  233. case 'ReflectionProperty':
  234. case 'ReflectionClassConstant':
  235. case 'Psy\Reflection\ReflectionClassConstant':
  236. $classReflector = $reflector->getDeclaringClass();
  237. $vars['__class'] = $classReflector->name;
  238. if ($classReflector->inNamespace()) {
  239. $vars['__namespace'] = $classReflector->getNamespaceName();
  240. }
  241. // no line for these, but this'll do
  242. if ($fileName = $reflector->getDeclaringClass()->getFileName()) {
  243. $vars['__file'] = $fileName;
  244. $vars['__dir'] = \dirname($fileName);
  245. }
  246. break;
  247. case 'Psy\Reflection\ReflectionConstant_':
  248. if ($reflector->inNamespace()) {
  249. $vars['__namespace'] = $reflector->getNamespaceName();
  250. }
  251. break;
  252. }
  253. if ($reflector instanceof \ReflectionClass || $reflector instanceof \ReflectionFunctionAbstract) {
  254. if ($fileName = $reflector->getFileName()) {
  255. $vars['__file'] = $fileName;
  256. $vars['__line'] = $reflector->getStartLine();
  257. $vars['__dir'] = \dirname($fileName);
  258. }
  259. }
  260. $this->context->setCommandScopeVariables($vars);
  261. }
  262. }