Input.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: yunwuxin <448901948@qq.com>
  10. // +----------------------------------------------------------------------
  11. namespace think\console;
  12. use think\console\input\Argument;
  13. use think\console\input\Definition;
  14. use think\console\input\Option;
  15. class Input
  16. {
  17. /**
  18. * @var Definition
  19. */
  20. protected $definition;
  21. /**
  22. * @var Option[]
  23. */
  24. protected $options = [];
  25. /**
  26. * @var Argument[]
  27. */
  28. protected $arguments = [];
  29. protected $interactive = true;
  30. private $tokens;
  31. private $parsed;
  32. public function __construct($argv = null)
  33. {
  34. if (null === $argv) {
  35. $argv = $_SERVER['argv'];
  36. // 去除命令名
  37. array_shift($argv);
  38. }
  39. $this->tokens = $argv;
  40. $this->definition = new Definition();
  41. }
  42. protected function setTokens(array $tokens)
  43. {
  44. $this->tokens = $tokens;
  45. }
  46. /**
  47. * 绑定实例
  48. * @param Definition $definition A InputDefinition instance
  49. */
  50. public function bind(Definition $definition)
  51. {
  52. $this->arguments = [];
  53. $this->options = [];
  54. $this->definition = $definition;
  55. $this->parse();
  56. }
  57. /**
  58. * 解析参数
  59. */
  60. protected function parse()
  61. {
  62. $parseOptions = true;
  63. $this->parsed = $this->tokens;
  64. while (null !== $token = array_shift($this->parsed)) {
  65. if ($parseOptions && '' == $token) {
  66. $this->parseArgument($token);
  67. } elseif ($parseOptions && '--' == $token) {
  68. $parseOptions = false;
  69. } elseif ($parseOptions && 0 === strpos($token, '--')) {
  70. $this->parseLongOption($token);
  71. } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
  72. $this->parseShortOption($token);
  73. } else {
  74. $this->parseArgument($token);
  75. }
  76. }
  77. }
  78. /**
  79. * 解析短选项
  80. * @param string $token 当前的指令.
  81. */
  82. private function parseShortOption($token)
  83. {
  84. $name = substr($token, 1);
  85. if (strlen($name) > 1) {
  86. if ($this->definition->hasShortcut($name[0])
  87. && $this->definition->getOptionForShortcut($name[0])->acceptValue()
  88. ) {
  89. $this->addShortOption($name[0], substr($name, 1));
  90. } else {
  91. $this->parseShortOptionSet($name);
  92. }
  93. } else {
  94. $this->addShortOption($name, null);
  95. }
  96. }
  97. /**
  98. * 解析短选项
  99. * @param string $name 当前指令
  100. * @throws \RuntimeException
  101. */
  102. private function parseShortOptionSet($name)
  103. {
  104. $len = strlen($name);
  105. for ($i = 0; $i < $len; ++$i) {
  106. if (!$this->definition->hasShortcut($name[$i])) {
  107. throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
  108. }
  109. $option = $this->definition->getOptionForShortcut($name[$i]);
  110. if ($option->acceptValue()) {
  111. $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
  112. break;
  113. } else {
  114. $this->addLongOption($option->getName(), null);
  115. }
  116. }
  117. }
  118. /**
  119. * 解析完整选项
  120. * @param string $token 当前指令
  121. */
  122. private function parseLongOption($token)
  123. {
  124. $name = substr($token, 2);
  125. if (false !== $pos = strpos($name, '=')) {
  126. $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1));
  127. } else {
  128. $this->addLongOption($name, null);
  129. }
  130. }
  131. /**
  132. * 解析参数
  133. * @param string $token 当前指令
  134. * @throws \RuntimeException
  135. */
  136. private function parseArgument($token)
  137. {
  138. $c = count($this->arguments);
  139. if ($this->definition->hasArgument($c)) {
  140. $arg = $this->definition->getArgument($c);
  141. $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
  142. } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
  143. $arg = $this->definition->getArgument($c - 1);
  144. $this->arguments[$arg->getName()][] = $token;
  145. } else {
  146. throw new \RuntimeException('Too many arguments.');
  147. }
  148. }
  149. /**
  150. * 添加一个短选项的值
  151. * @param string $shortcut 短名称
  152. * @param mixed $value 值
  153. * @throws \RuntimeException
  154. */
  155. private function addShortOption($shortcut, $value)
  156. {
  157. if (!$this->definition->hasShortcut($shortcut)) {
  158. throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
  159. }
  160. $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
  161. }
  162. /**
  163. * 添加一个完整选项的值
  164. * @param string $name 选项名
  165. * @param mixed $value 值
  166. * @throws \RuntimeException
  167. */
  168. private function addLongOption($name, $value)
  169. {
  170. if (!$this->definition->hasOption($name)) {
  171. throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name));
  172. }
  173. $option = $this->definition->getOption($name);
  174. if (false === $value) {
  175. $value = null;
  176. }
  177. if (null !== $value && !$option->acceptValue()) {
  178. throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value));
  179. }
  180. if (null === $value && $option->acceptValue() && count($this->parsed)) {
  181. $next = array_shift($this->parsed);
  182. if (isset($next[0]) && '-' !== $next[0]) {
  183. $value = $next;
  184. } elseif (empty($next)) {
  185. $value = '';
  186. } else {
  187. array_unshift($this->parsed, $next);
  188. }
  189. }
  190. if (null === $value) {
  191. if ($option->isValueRequired()) {
  192. throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name));
  193. }
  194. if (!$option->isArray()) {
  195. $value = $option->isValueOptional() ? $option->getDefault() : true;
  196. }
  197. }
  198. if ($option->isArray()) {
  199. $this->options[$name][] = $value;
  200. } else {
  201. $this->options[$name] = $value;
  202. }
  203. }
  204. /**
  205. * 获取第一个参数
  206. * @return string|null
  207. */
  208. public function getFirstArgument()
  209. {
  210. foreach ($this->tokens as $token) {
  211. if ($token && '-' === $token[0]) {
  212. continue;
  213. }
  214. return $token;
  215. }
  216. return;
  217. }
  218. /**
  219. * 检查原始参数是否包含某个值
  220. * @param string|array $values 需要检查的值
  221. * @return bool
  222. */
  223. public function hasParameterOption($values)
  224. {
  225. $values = (array) $values;
  226. foreach ($this->tokens as $token) {
  227. foreach ($values as $value) {
  228. if ($token === $value || 0 === strpos($token, $value . '=')) {
  229. return true;
  230. }
  231. }
  232. }
  233. return false;
  234. }
  235. /**
  236. * 获取原始选项的值
  237. * @param string|array $values 需要检查的值
  238. * @param mixed $default 默认值
  239. * @return mixed The option value
  240. */
  241. public function getParameterOption($values, $default = false)
  242. {
  243. $values = (array) $values;
  244. $tokens = $this->tokens;
  245. while (0 < count($tokens)) {
  246. $token = array_shift($tokens);
  247. foreach ($values as $value) {
  248. if ($token === $value || 0 === strpos($token, $value . '=')) {
  249. if (false !== $pos = strpos($token, '=')) {
  250. return substr($token, $pos + 1);
  251. }
  252. return array_shift($tokens);
  253. }
  254. }
  255. }
  256. return $default;
  257. }
  258. /**
  259. * 验证输入
  260. * @throws \RuntimeException
  261. */
  262. public function validate()
  263. {
  264. if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) {
  265. throw new \RuntimeException('Not enough arguments.');
  266. }
  267. }
  268. /**
  269. * 检查输入是否是交互的
  270. * @return bool
  271. */
  272. public function isInteractive()
  273. {
  274. return $this->interactive;
  275. }
  276. /**
  277. * 设置输入的交互
  278. * @param bool
  279. */
  280. public function setInteractive($interactive)
  281. {
  282. $this->interactive = (bool) $interactive;
  283. }
  284. /**
  285. * 获取所有的参数
  286. * @return Argument[]
  287. */
  288. public function getArguments()
  289. {
  290. return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
  291. }
  292. /**
  293. * 根据名称获取参数
  294. * @param string $name 参数名
  295. * @return mixed
  296. * @throws \InvalidArgumentException
  297. */
  298. public function getArgument($name)
  299. {
  300. if (!$this->definition->hasArgument($name)) {
  301. throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
  302. }
  303. return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)
  304. ->getDefault();
  305. }
  306. /**
  307. * 设置参数的值
  308. * @param string $name 参数名
  309. * @param string $value 值
  310. * @throws \InvalidArgumentException
  311. */
  312. public function setArgument($name, $value)
  313. {
  314. if (!$this->definition->hasArgument($name)) {
  315. throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
  316. }
  317. $this->arguments[$name] = $value;
  318. }
  319. /**
  320. * 检查是否存在某个参数
  321. * @param string|int $name 参数名或位置
  322. * @return bool
  323. */
  324. public function hasArgument($name)
  325. {
  326. return $this->definition->hasArgument($name);
  327. }
  328. /**
  329. * 获取所有的选项
  330. * @return Option[]
  331. */
  332. public function getOptions()
  333. {
  334. return array_merge($this->definition->getOptionDefaults(), $this->options);
  335. }
  336. /**
  337. * 获取选项值
  338. * @param string $name 选项名称
  339. * @return mixed
  340. * @throws \InvalidArgumentException
  341. */
  342. public function getOption($name)
  343. {
  344. if (!$this->definition->hasOption($name)) {
  345. throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
  346. }
  347. return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
  348. }
  349. /**
  350. * 设置选项值
  351. * @param string $name 选项名
  352. * @param string|bool $value 值
  353. * @throws \InvalidArgumentException
  354. */
  355. public function setOption($name, $value)
  356. {
  357. if (!$this->definition->hasOption($name)) {
  358. throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
  359. }
  360. $this->options[$name] = $value;
  361. }
  362. /**
  363. * 是否有某个选项
  364. * @param string $name 选项名
  365. * @return bool
  366. */
  367. public function hasOption($name)
  368. {
  369. return $this->definition->hasOption($name) && isset($this->options[$name]);
  370. }
  371. /**
  372. * 转义指令
  373. * @param string $token
  374. * @return string
  375. */
  376. public function escapeToken($token)
  377. {
  378. return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
  379. }
  380. /**
  381. * 返回传递给命令的参数的字符串
  382. * @return string
  383. */
  384. public function __toString()
  385. {
  386. $tokens = array_map(function ($token) {
  387. if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
  388. return $match[1] . $this->escapeToken($match[2]);
  389. }
  390. if ($token && '-' !== $token[0]) {
  391. return $this->escapeToken($token);
  392. }
  393. return $token;
  394. }, $this->tokens);
  395. return implode(' ', $tokens);
  396. }
  397. }