PageRenderTime 32ms CodeModel.GetById 37ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Console/CoreCommand.php

https://gitlab.com/cpteam/console
PHP | 445 lines | 263 code | 84 blank | 98 comment | 16 complexity | b936dea256d390f426e9b848def13412 MD5 | raw file
  1. <?php
  2. namespace CPTeam\Console;
  3. use Symfony\Component\Console\Command\Command;
  4. use Symfony\Component\Console\Formatter\OutputFormatterStyle as OFS;
  5. use Symfony\Component\Console\Input\InputInterface;
  6. use Symfony\Component\Console\Input\InputOption;
  7. use Symfony\Component\Console\Output\OutputInterface;
  8. use Symfony\Component\Console\Question\ConfirmationQuestion;
  9. use Symfony\Component\Console\Question\Question;
  10. use Tracy\Debugger;
  11. /**
  12. * Class Core
  13. *
  14. * Obal pro konzolove prikazy
  15. *
  16. * @package CPTeam\Symfony\Console
  17. *
  18. * @release 0.1.64
  19. */
  20. abstract class CoreCommand extends Command
  21. {
  22. const TABLE_SIZE = 60;
  23. const PRETTY = 'pretty';
  24. /** @var callable[] */
  25. public static $onException = [];
  26. /** @var callable[] */
  27. public static $onError = [];
  28. /** @var callable[] */
  29. private $beforeRun = [];
  30. /** @var callable[] */
  31. private $afterRun = [];
  32. /** @var InputInterface */
  33. private $input;
  34. /** @var OutputInterface */
  35. private $output;
  36. private $tableSize = self::TABLE_SIZE;
  37. protected $timer = 0;
  38. private $state = 0;
  39. private $configureInit = false;
  40. const STATE_NONE = 0;
  41. const STATE_PREPARE = 1;
  42. const STATE_PROCESS = 2;
  43. const STATE_COMPLETED = 3;
  44. const STATE_TERMINATED = 10;
  45. const STATE_ERROR = 20;
  46. abstract protected function runCmd(InputInterface $input, OutputInterface $output);
  47. protected function configure()
  48. {
  49. parent::configure();
  50. $this->addOption(
  51. self::PRETTY,
  52. null,
  53. InputOption::VALUE_NONE,
  54. 'Output without system logs, etc.'
  55. );
  56. $this->addOption(
  57. "output",
  58. "o",
  59. InputOption::VALUE_REQUIRED,
  60. "Output path",
  61. false
  62. );
  63. $this->configureInit = true;
  64. }
  65. /**
  66. * @param \Symfony\Component\Console\Input\InputInterface $input
  67. * @param \Symfony\Component\Console\Output\OutputInterface $output
  68. *
  69. * @return int
  70. */
  71. final protected function execute(InputInterface $input, OutputInterface $output): ?int
  72. {
  73. if ($this->configureInit === false) {
  74. throw new LogicException("You missed run parent::configure().");
  75. }
  76. $styles = [
  77. 'system' => new OFS('cyan', 'default', []),
  78. 'task' => new OFS('magenta', 'default', []),
  79. 'verbose' => new OFS('yellow', 'default', []),
  80. 'success' => new OFS('green', 'default', []),
  81. 'debug' => new OFS('default', 'blue', []),
  82. 'error' => new OFS('red', 'default', ["bold"]),
  83. 'cyan' => new OFS('cyan', 'default', ["bold"]),
  84. 'magenta' => new OFS('magenta', 'default', ["bold"]),
  85. 'green' => new OFS('green', 'default', ["bold"]),
  86. 'blue' => new OFS('blue', 'default', ["bold"]),
  87. 'yellow' => new OFS('yellow', 'default', ["bold"]),
  88. 'red' => new OFS('red', 'default', []),
  89. 'darkCyan' => new OFS('cyan', 'default', []),
  90. 'darkMagenta' => new OFS('magenta', 'default', []),
  91. 'darkGreen' => new OFS('green', 'default', []),
  92. 'darkBlue' => new OFS('blue', 'default', []),
  93. 'darkYellow' => new OFS('yellow', 'default', []),
  94. 'darkRed' => new OFS('red', 'default', []),
  95. ];
  96. if ($input->getOption("output")) {
  97. $output = new FileOutput($output, $input->getOption("output"));
  98. }
  99. foreach ($styles as $name => $style) {
  100. $output->getFormatter()->setStyle($name, $style);
  101. }
  102. $this->input = $input;
  103. $this->output = $output;
  104. $pretty = $this->getInput()->getOption(self::PRETTY);
  105. if ($pretty === false) {
  106. try {
  107. $this->state = self::STATE_PREPARE;
  108. $output->writeln("<yellow>" . str_repeat("=", $this->getTableSize() + strlen($this->getName()) + 3) . "</yellow>");
  109. $this->logSystem("<task>" . $this->getName() . "</task> at " . date("d.m.Y H:i:s"));
  110. $this->logHr();
  111. $this->timer = 0 - microtime(true);
  112. $result = 1;
  113. try {
  114. foreach ($this->beforeRun as $call) {
  115. $call($input, $output);
  116. }
  117. $this->state = self::STATE_PROCESS;
  118. $result = $this->runCmd($input, $output);
  119. $this->state = self::STATE_COMPLETED;
  120. foreach ($this->afterRun as $call) {
  121. $call($input, $output);
  122. }
  123. } catch (TerminateException $e) {
  124. $this->state = self::STATE_TERMINATED;
  125. $this->logHr();
  126. $this->logError("<red>Script terminated</red>");
  127. $this->logError($e->getMessage());
  128. }
  129. $this->timer += microtime(true);
  130. $this->logHr();
  131. $this->logSystem("Task <magenta>" . $this->getName() . "</magenta> complete" . ($this->isTerminated() ? " (<red>Terminated</red>)" : ""));
  132. $this->logSystem(
  133. $this->table("Execution time", "<yellow>" . number_format(round($this->timer * 1000, 3), 3, ',', ' ') . " ms</yellow>")
  134. );
  135. $pid = getmypid();
  136. $this->logSystem(
  137. $this->table("Memory allocated (php)", "<yellow>" . round(memory_get_usage() / 1024 / 1024, 2) . " MB</yellow>")
  138. );
  139. $this->logSystem(
  140. $this->table("Memory usage (process)", "<yellow>" . round((int)`ps -p $pid v | awk 'END{print $8}'` / 1024, 2) . " MB</yellow>")
  141. );
  142. $output->writeln("<yellow>" . str_repeat("=", $this->getTableSize() + strlen($this->getName()) + 3) . "</yellow>");
  143. return $result;
  144. } catch (\Exception $e) {
  145. /** @var callable $callback */
  146. foreach (self::$onException as $callback) {
  147. if (is_callable($callback)) {
  148. $callback($e);
  149. }
  150. }
  151. $this->logError($e);
  152. return 1;
  153. } catch (\Error $e) {
  154. /** @var callable $callback */
  155. foreach (self::$onError as $callback) {
  156. if (is_callable($callback)) {
  157. $callback($e);
  158. }
  159. }
  160. $this->logError($e);
  161. return 1;
  162. }
  163. } else {
  164. $this->runCmd($input, $output);
  165. }
  166. }
  167. /**
  168. * @param $msg
  169. *
  170. * @throws TerminateException
  171. */
  172. protected function terminate($msg)
  173. {
  174. throw new TerminateException($msg);
  175. }
  176. protected function setDebug()
  177. {
  178. Debugger::$productionMode = Debugger::DEVELOPMENT;
  179. }
  180. /**
  181. * @param string $msg
  182. * @param int $verbosity
  183. */
  184. protected function log($msg = "", $verbosity = OutputInterface::VERBOSITY_NORMAL)
  185. {
  186. if ($this->output->getVerbosity() >= $verbosity) {
  187. if (is_string($msg) || is_numeric($msg)) {
  188. $this->output->writeln('[<task>' . $this->getName() . '</task>] ' . $msg);
  189. } else {
  190. //$d = dump($msg, true);
  191. $x = Debugger::dump($msg, true);
  192. foreach (explode("\n", $x) as $msg) {
  193. $this->log($msg);
  194. }
  195. }
  196. }
  197. }
  198. /**
  199. * @param $msg
  200. */
  201. protected function logVerbose($msg)
  202. {
  203. $this->log("<verbose>$msg</verbose>", OutputInterface::VERBOSITY_VERBOSE);
  204. }
  205. /**
  206. * @param $msg
  207. */
  208. protected function logDebug($msg)
  209. {
  210. $this->log("<debug>$msg</debug>", OutputInterface::VERBOSITY_DEBUG);
  211. }
  212. /**
  213. * @param $msg
  214. */
  215. protected function logError($msg)
  216. {
  217. if ($msg instanceof \Error || $msg instanceof \Exception) {
  218. $class = get_class($msg);
  219. $this->log(
  220. "<error>\n\n[Exception]: $class" . "\n" .
  221. "{$msg->getMessage()}</error>" . "\n\n" .
  222. "<red>{$msg->getTraceAsString()}</red>"
  223. );
  224. } else {
  225. $this->log("<error>$msg</error>", OutputInterface::VERBOSITY_NORMAL);
  226. }
  227. }
  228. /**
  229. * @param $msg
  230. */
  231. protected function logSuccess($msg)
  232. {
  233. $this->log("<success>$msg</success>", OutputInterface::VERBOSITY_NORMAL);
  234. }
  235. protected function logHr()
  236. {
  237. $this->logSystem(str_repeat("-", $this->getTableSize()));
  238. }
  239. /**
  240. * @param $left
  241. * @param $right
  242. * @param string $spaceChar
  243. *
  244. * @return string
  245. */
  246. protected function table($left, $right, $spaceChar = " ")
  247. {
  248. return $left .
  249. str_repeat(
  250. $spaceChar,
  251. max(1, $this->getTableSize() - mb_strlen(strip_tags($left)) - mb_strlen(strip_tags($right)))
  252. ) .
  253. $right;
  254. }
  255. /**
  256. * @param $msg
  257. */
  258. private function logSystem(string $msg): void
  259. {
  260. $this->log("<system>$msg</system>", OutputInterface::VERBOSITY_QUIET);
  261. }
  262. /**
  263. * @return InputInterface
  264. */
  265. protected function getInput(): InputInterface
  266. {
  267. return $this->input;
  268. }
  269. /**
  270. * @return OutputInterface
  271. */
  272. protected function getOutput(): OutputInterface
  273. {
  274. return $this->output;
  275. }
  276. /**
  277. * @param $size
  278. *
  279. * @return $this
  280. */
  281. protected function setTableSize($size)
  282. {
  283. $this->tableSize = $size;
  284. return $this;
  285. }
  286. /**
  287. * @return int
  288. */
  289. protected function getTableSize()
  290. {
  291. if ($this->tableSize === null) {
  292. $this->tableSize = self::TABLE_SIZE;
  293. }
  294. return $this->tableSize;
  295. }
  296. /**
  297. * @param $name
  298. * @param $quest
  299. * @param null $default
  300. *
  301. * @return mixed
  302. * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
  303. * @throws \Symfony\Component\Console\Exception\LogicException
  304. */
  305. protected function get($name, $quest, $default = null)
  306. {
  307. $option = $this->getInput()->getOption($name);
  308. if ($option) {
  309. return $option;
  310. }
  311. $q = new Question($quest, $default);
  312. $helper = $this->questionHelper();
  313. return $helper->ask($this->getInput(), $this->getOutput(), $q);
  314. }
  315. /**
  316. * @param $name
  317. * @param $quest
  318. * @param bool $default
  319. *
  320. * @return mixed
  321. * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
  322. * @throws \Symfony\Component\Console\Exception\LogicException
  323. */
  324. protected function confirm($name, $quest, $default = false)
  325. {
  326. $option = $this->getInput()->getOption($name);
  327. if ($option) {
  328. return $option;
  329. }
  330. $q = new ConfirmationQuestion($quest, $default);
  331. $helper = $this->questionHelper();
  332. return $helper->ask($this->getInput(), $this->getOutput(), $q);
  333. }
  334. /**
  335. * @return mixed
  336. * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
  337. * @throws \Symfony\Component\Console\Exception\LogicException
  338. */
  339. protected function questionHelper()
  340. {
  341. return $this->getHelper("question");
  342. }
  343. public function isTerminated(): bool
  344. {
  345. return $this->state === self::STATE_TERMINATED;
  346. }
  347. /**
  348. * @param \callable[] $beforeRun
  349. */
  350. public function addBeforeRun(callable $beforeRun)
  351. {
  352. $this->beforeRun[] = $beforeRun;
  353. }
  354. /**
  355. * @param \callable[] $afterRun
  356. */
  357. public function addAfterRun(callable $afterRun)
  358. {
  359. $this->afterRun[] = $afterRun;
  360. }
  361. }