Context.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <?php
  2. /*
  3. * This file is part of Psy Shell.
  4. *
  5. * (c) 2012-2022 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;
  11. /**
  12. * The Shell execution context.
  13. *
  14. * This class encapsulates the current variables, most recent return value and
  15. * exception, and the current namespace.
  16. */
  17. class Context
  18. {
  19. private static $specialNames = ['_', '_e', '__out', '__psysh__', 'this'];
  20. // Include a very limited number of command-scope magic variable names.
  21. // This might be a bad idea, but future me can sort it out.
  22. private static $commandScopeNames = [
  23. '__function', '__method', '__class', '__namespace', '__file', '__line', '__dir',
  24. ];
  25. private $scopeVariables = [];
  26. private $commandScopeVariables = [];
  27. private $returnValue;
  28. private $lastException;
  29. private $lastStdout;
  30. private $boundObject;
  31. private $boundClass;
  32. /**
  33. * Get a context variable.
  34. *
  35. * @throws \InvalidArgumentException If the variable is not found in the current context
  36. *
  37. * @param string $name
  38. *
  39. * @return mixed
  40. */
  41. public function get(string $name)
  42. {
  43. switch ($name) {
  44. case '_':
  45. return $this->returnValue;
  46. case '_e':
  47. if (isset($this->lastException)) {
  48. return $this->lastException;
  49. }
  50. break;
  51. case '__out':
  52. if (isset($this->lastStdout)) {
  53. return $this->lastStdout;
  54. }
  55. break;
  56. case 'this':
  57. if (isset($this->boundObject)) {
  58. return $this->boundObject;
  59. }
  60. break;
  61. case '__function':
  62. case '__method':
  63. case '__class':
  64. case '__namespace':
  65. case '__file':
  66. case '__line':
  67. case '__dir':
  68. if (\array_key_exists($name, $this->commandScopeVariables)) {
  69. return $this->commandScopeVariables[$name];
  70. }
  71. break;
  72. default:
  73. if (\array_key_exists($name, $this->scopeVariables)) {
  74. return $this->scopeVariables[$name];
  75. }
  76. break;
  77. }
  78. throw new \InvalidArgumentException('Unknown variable: $'.$name);
  79. }
  80. /**
  81. * Get all defined variables.
  82. *
  83. * @return array
  84. */
  85. public function getAll(): array
  86. {
  87. return \array_merge($this->scopeVariables, $this->getSpecialVariables());
  88. }
  89. /**
  90. * Get all defined magic variables: $_, $_e, $__out, $__class, $__file, etc.
  91. *
  92. * @return array
  93. */
  94. public function getSpecialVariables(): array
  95. {
  96. $vars = [
  97. '_' => $this->returnValue,
  98. ];
  99. if (isset($this->lastException)) {
  100. $vars['_e'] = $this->lastException;
  101. }
  102. if (isset($this->lastStdout)) {
  103. $vars['__out'] = $this->lastStdout;
  104. }
  105. if (isset($this->boundObject)) {
  106. $vars['this'] = $this->boundObject;
  107. }
  108. return \array_merge($vars, $this->commandScopeVariables);
  109. }
  110. /**
  111. * Set all scope variables.
  112. *
  113. * This method does *not* set any of the magic variables: $_, $_e, $__out,
  114. * $__class, $__file, etc.
  115. *
  116. * @param array $vars
  117. */
  118. public function setAll(array $vars)
  119. {
  120. foreach (self::$specialNames as $key) {
  121. unset($vars[$key]);
  122. }
  123. foreach (self::$commandScopeNames as $key) {
  124. unset($vars[$key]);
  125. }
  126. $this->scopeVariables = $vars;
  127. }
  128. /**
  129. * Set the most recent return value.
  130. *
  131. * @param mixed $value
  132. */
  133. public function setReturnValue($value)
  134. {
  135. $this->returnValue = $value;
  136. }
  137. /**
  138. * Get the most recent return value.
  139. *
  140. * @return mixed
  141. */
  142. public function getReturnValue()
  143. {
  144. return $this->returnValue;
  145. }
  146. /**
  147. * Set the most recent Exception.
  148. *
  149. * @param \Exception $e
  150. */
  151. public function setLastException(\Exception $e)
  152. {
  153. $this->lastException = $e;
  154. }
  155. /**
  156. * Get the most recent Exception.
  157. *
  158. * @throws \InvalidArgumentException If no Exception has been caught
  159. *
  160. * @return \Exception|null
  161. */
  162. public function getLastException()
  163. {
  164. if (!isset($this->lastException)) {
  165. throw new \InvalidArgumentException('No most-recent exception');
  166. }
  167. return $this->lastException;
  168. }
  169. /**
  170. * Set the most recent output from evaluated code.
  171. *
  172. * @param string $lastStdout
  173. */
  174. public function setLastStdout(string $lastStdout)
  175. {
  176. $this->lastStdout = $lastStdout;
  177. }
  178. /**
  179. * Get the most recent output from evaluated code.
  180. *
  181. * @throws \InvalidArgumentException If no output has happened yet
  182. *
  183. * @return string|null
  184. */
  185. public function getLastStdout()
  186. {
  187. if (!isset($this->lastStdout)) {
  188. throw new \InvalidArgumentException('No most-recent output');
  189. }
  190. return $this->lastStdout;
  191. }
  192. /**
  193. * Set the bound object ($this variable) for the interactive shell.
  194. *
  195. * Note that this unsets the bound class, if any exists.
  196. *
  197. * @param object|null $boundObject
  198. */
  199. public function setBoundObject($boundObject)
  200. {
  201. $this->boundObject = \is_object($boundObject) ? $boundObject : null;
  202. $this->boundClass = null;
  203. }
  204. /**
  205. * Get the bound object ($this variable) for the interactive shell.
  206. *
  207. * @return object|null
  208. */
  209. public function getBoundObject()
  210. {
  211. return $this->boundObject;
  212. }
  213. /**
  214. * Set the bound class (self) for the interactive shell.
  215. *
  216. * Note that this unsets the bound object, if any exists.
  217. *
  218. * @param string|null $boundClass
  219. */
  220. public function setBoundClass($boundClass)
  221. {
  222. $this->boundClass = (\is_string($boundClass) && $boundClass !== '') ? $boundClass : null;
  223. $this->boundObject = null;
  224. }
  225. /**
  226. * Get the bound class (self) for the interactive shell.
  227. *
  228. * @return string|null
  229. */
  230. public function getBoundClass()
  231. {
  232. return $this->boundClass;
  233. }
  234. /**
  235. * Set command-scope magic variables: $__class, $__file, etc.
  236. *
  237. * @param array $commandScopeVariables
  238. */
  239. public function setCommandScopeVariables(array $commandScopeVariables)
  240. {
  241. $vars = [];
  242. foreach ($commandScopeVariables as $key => $value) {
  243. // kind of type check
  244. if (\is_scalar($value) && \in_array($key, self::$commandScopeNames)) {
  245. $vars[$key] = $value;
  246. }
  247. }
  248. $this->commandScopeVariables = $vars;
  249. }
  250. /**
  251. * Get command-scope magic variables: $__class, $__file, etc.
  252. *
  253. * @return array
  254. */
  255. public function getCommandScopeVariables(): array
  256. {
  257. return $this->commandScopeVariables;
  258. }
  259. /**
  260. * Get unused command-scope magic variables names: __class, __file, etc.
  261. *
  262. * This is used by the shell to unset old command-scope variables after a
  263. * new batch is set.
  264. *
  265. * @return array Array of unused variable names
  266. */
  267. public function getUnusedCommandScopeVariableNames(): array
  268. {
  269. return \array_diff(self::$commandScopeNames, \array_keys($this->commandScopeVariables));
  270. }
  271. /**
  272. * Check whether a variable name is a magic variable.
  273. *
  274. * @param string $name
  275. *
  276. * @return bool
  277. */
  278. public static function isSpecialVariableName(string $name): bool
  279. {
  280. return \in_array($name, self::$specialNames) || \in_array($name, self::$commandScopeNames);
  281. }
  282. }