Context.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. <?php
  2. /*
  3. * This file is part of Psy Shell.
  4. *
  5. * (c) 2012-2023 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. public function getAll(): array
  84. {
  85. return \array_merge($this->scopeVariables, $this->getSpecialVariables());
  86. }
  87. /**
  88. * Get all defined magic variables: $_, $_e, $__out, $__class, $__file, etc.
  89. */
  90. public function getSpecialVariables(): array
  91. {
  92. $vars = [
  93. '_' => $this->returnValue,
  94. ];
  95. if (isset($this->lastException)) {
  96. $vars['_e'] = $this->lastException;
  97. }
  98. if (isset($this->lastStdout)) {
  99. $vars['__out'] = $this->lastStdout;
  100. }
  101. if (isset($this->boundObject)) {
  102. $vars['this'] = $this->boundObject;
  103. }
  104. return \array_merge($vars, $this->commandScopeVariables);
  105. }
  106. /**
  107. * Set all scope variables.
  108. *
  109. * This method does *not* set any of the magic variables: $_, $_e, $__out,
  110. * $__class, $__file, etc.
  111. *
  112. * @param array $vars
  113. */
  114. public function setAll(array $vars)
  115. {
  116. foreach (self::$specialNames as $key) {
  117. unset($vars[$key]);
  118. }
  119. foreach (self::$commandScopeNames as $key) {
  120. unset($vars[$key]);
  121. }
  122. $this->scopeVariables = $vars;
  123. }
  124. /**
  125. * Set the most recent return value.
  126. *
  127. * @param mixed $value
  128. */
  129. public function setReturnValue($value)
  130. {
  131. $this->returnValue = $value;
  132. }
  133. /**
  134. * Get the most recent return value.
  135. *
  136. * @return mixed
  137. */
  138. public function getReturnValue()
  139. {
  140. return $this->returnValue;
  141. }
  142. /**
  143. * Set the most recent Exception or Error.
  144. *
  145. * @param \Throwable $e
  146. */
  147. public function setLastException(\Throwable $e)
  148. {
  149. $this->lastException = $e;
  150. }
  151. /**
  152. * Get the most recent Exception or Error.
  153. *
  154. * @throws \InvalidArgumentException If no Exception has been caught
  155. *
  156. * @return \Throwable|null
  157. */
  158. public function getLastException()
  159. {
  160. if (!isset($this->lastException)) {
  161. throw new \InvalidArgumentException('No most-recent exception');
  162. }
  163. return $this->lastException;
  164. }
  165. /**
  166. * Set the most recent output from evaluated code.
  167. *
  168. * @param string $lastStdout
  169. */
  170. public function setLastStdout(string $lastStdout)
  171. {
  172. $this->lastStdout = $lastStdout;
  173. }
  174. /**
  175. * Get the most recent output from evaluated code.
  176. *
  177. * @throws \InvalidArgumentException If no output has happened yet
  178. *
  179. * @return string|null
  180. */
  181. public function getLastStdout()
  182. {
  183. if (!isset($this->lastStdout)) {
  184. throw new \InvalidArgumentException('No most-recent output');
  185. }
  186. return $this->lastStdout;
  187. }
  188. /**
  189. * Set the bound object ($this variable) for the interactive shell.
  190. *
  191. * Note that this unsets the bound class, if any exists.
  192. *
  193. * @param object|null $boundObject
  194. */
  195. public function setBoundObject($boundObject)
  196. {
  197. $this->boundObject = \is_object($boundObject) ? $boundObject : null;
  198. $this->boundClass = null;
  199. }
  200. /**
  201. * Get the bound object ($this variable) for the interactive shell.
  202. *
  203. * @return object|null
  204. */
  205. public function getBoundObject()
  206. {
  207. return $this->boundObject;
  208. }
  209. /**
  210. * Set the bound class (self) for the interactive shell.
  211. *
  212. * Note that this unsets the bound object, if any exists.
  213. *
  214. * @param string|null $boundClass
  215. */
  216. public function setBoundClass($boundClass)
  217. {
  218. $this->boundClass = (\is_string($boundClass) && $boundClass !== '') ? $boundClass : null;
  219. $this->boundObject = null;
  220. }
  221. /**
  222. * Get the bound class (self) for the interactive shell.
  223. *
  224. * @return string|null
  225. */
  226. public function getBoundClass()
  227. {
  228. return $this->boundClass;
  229. }
  230. /**
  231. * Set command-scope magic variables: $__class, $__file, etc.
  232. *
  233. * @param array $commandScopeVariables
  234. */
  235. public function setCommandScopeVariables(array $commandScopeVariables)
  236. {
  237. $vars = [];
  238. foreach ($commandScopeVariables as $key => $value) {
  239. // kind of type check
  240. if (\is_scalar($value) && \in_array($key, self::$commandScopeNames)) {
  241. $vars[$key] = $value;
  242. }
  243. }
  244. $this->commandScopeVariables = $vars;
  245. }
  246. /**
  247. * Get command-scope magic variables: $__class, $__file, etc.
  248. */
  249. public function getCommandScopeVariables(): array
  250. {
  251. return $this->commandScopeVariables;
  252. }
  253. /**
  254. * Get unused command-scope magic variables names: __class, __file, etc.
  255. *
  256. * This is used by the shell to unset old command-scope variables after a
  257. * new batch is set.
  258. *
  259. * @return array Array of unused variable names
  260. */
  261. public function getUnusedCommandScopeVariableNames(): array
  262. {
  263. return \array_diff(self::$commandScopeNames, \array_keys($this->commandScopeVariables));
  264. }
  265. /**
  266. * Check whether a variable name is a magic variable.
  267. *
  268. * @param string $name
  269. */
  270. public static function isSpecialVariableName(string $name): bool
  271. {
  272. return \in_array($name, self::$specialNames) || \in_array($name, self::$commandScopeNames);
  273. }
  274. }