PageRenderTime 42ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/itech_av4/protected/extensions/PHPDocCrontab/PHPDocCrontab.php

https://gitlab.com/pawit1357/prdapp
PHP | 314 lines | 188 code | 25 blank | 101 comment | 18 complexity | fb0120272d6836d3074c619f44f7b0f5 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, GPL-3.0
  1. <?php
  2. /**
  3. * Yii Framework extension. Better installing console commands as cron jobs.
  4. *
  5. * @author Evgeny Blinov <e.a.blinov@gmail.com>
  6. * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  7. */
  8. /**
  9. * PHPDocCrontab is a CConsoleCommand to automaticly running marked actions.
  10. *
  11. * @author Evgeny Blinov <e.a.blinov@gmail.com>
  12. * @package PHPDocCrontab
  13. */
  14. class PHPDocCrontab extends CConsoleCommand {
  15. /**
  16. * @var string PHPDoc tag prefix for using by PHPDocCrontab extension.
  17. */
  18. public $tagPrefix = 'cron';
  19. /**
  20. * @var string PHP interpriter path (if empty, path will be checked automaticly)
  21. */
  22. public $interpreterPath = null;
  23. /**
  24. * @var string path to writing logs
  25. */
  26. public $logsDir = null;
  27. /**
  28. * Placeholders:
  29. * %L - logsDir path
  30. * %C - name of command
  31. * %A - name of action
  32. * %P - pid of runner-script (current)
  33. * %D(string formatted as arg of date() function) - formatted date
  34. * @var string mask log file name
  35. */
  36. public $logFileName = '%L/%C.%A.log';
  37. /**
  38. * @var string Bootstrap script path (if empty, current command runner will be used)
  39. */
  40. public $bootstrapScript = null;
  41. /**
  42. * @var string Timestamp used as current datetime
  43. * @see http://php.net/manual/en/function.strtotime.php
  44. */
  45. public $timestamp = 'now';
  46. /**
  47. * @var string the name of the default action. Defaults to 'run'.
  48. */
  49. public $defaultAction = 'run';
  50. /**
  51. * Initialize empty config parameters.
  52. */
  53. public function init() {
  54. parent::init();
  55. //Checking PHP interpriter path
  56. if ($this->interpreterPath === null){
  57. if ($this->isWindowsOS()){
  58. //Windows OS
  59. $this->interpreterPath = 'php.exe';
  60. }
  61. else{
  62. //nix based OS
  63. $this->interpreterPath = '/usr/bin/env php';
  64. }
  65. }
  66. //Checking logs directory
  67. if ($this->logsDir === null){
  68. $this->logsDir = Yii::app()->getRuntimePath();
  69. }
  70. //Checking bootstrap script
  71. if ($this->bootstrapScript === null){
  72. $this->bootstrapScript = realpath($this->getCommandRunner()->getScriptName());
  73. }
  74. }
  75. /**
  76. * Provides the command description.
  77. * @return string the command description.
  78. */
  79. public function getHelp() {
  80. $commandUsage = $this->getCommandRunner()->getScriptName().' '.$this->getName();
  81. return <<<RAW
  82. Usage: {$commandUsage} <action>
  83. Actions:
  84. view <tags> - Show active tasks, specified by tags.
  85. run <options> <tags> - Run suitable tasks, specified by tags (default action).
  86. help - Show this help.
  87. Tags:
  88. [tag1] [tag2] [...] [tagN] - List of tags
  89. Options:
  90. [--tagPrefix=value]
  91. [--interpreterPath=value]
  92. [--logsDir=value]
  93. [--logFileName=value]
  94. [--bootstrapScript=value]
  95. [--timestamp=value]
  96. RAW;
  97. }
  98. /**
  99. * Transform string datetime expressions to array sets
  100. *
  101. * @param array $parameters
  102. * @return array
  103. */
  104. protected function transformDatePieces(array $parameters){
  105. $dimensions = array(
  106. array(0,59), //Minutes
  107. array(0,23), //Hours
  108. array(1,31), //Days
  109. array(1,12), //Months
  110. array(0,6), //Weekdays
  111. );
  112. foreach ($parameters AS $n => &$repeat) {
  113. list($repeat, $every) = explode('\\', $repeat, 2) + array(false, 1);
  114. if ($repeat === '*') $repeat = range($dimensions[$n][0], $dimensions[$n][1]);
  115. else {
  116. $repeatPiece = array();
  117. foreach (explode(',', $repeat) as $piece) {
  118. $piece = explode('-', $piece, 2);
  119. if (count($piece) === 2) $repeatPiece = array_merge($repeatPiece, range($piece[0], $piece[1]));
  120. else $repeatPiece[] = $piece[0];
  121. }
  122. $repeat = $repeatPiece;
  123. }
  124. if ($every > 1) foreach ($repeat AS $key => $piece){
  125. if ($piece%$every !== 0) unset($repeat[$key]);
  126. }
  127. }
  128. return $parameters;
  129. }
  130. /**
  131. * Parsing and filtering PHPDoc comments.
  132. *
  133. * @param string $comment Raw PHPDoc comment
  134. * @return array List of valid tags
  135. */
  136. protected function parseDocComment($comment){
  137. if (empty($comment)) return array();
  138. //Forming pattern based on $this->tagPrefix
  139. $pattern = '#^\s*\*\s+@('.$this->tagPrefix.'(-(\w+))?)\s*(.*?)\s*$#im';
  140. //Miss tags:
  141. //cron, cron-tags, cron-args, cron-strout, cron-stderr
  142. if (preg_match_all($pattern, $comment, $matches, PREG_SET_ORDER)){
  143. foreach ($matches AS $match) $return[$match[3]?$match[3]:0] = $match[4];
  144. if (isset($return[0])){
  145. $return['_raw'] = preg_split('#\s+#', $return[0], 5);
  146. $return[0] = $this->transformDatePieces($return['_raw']);
  147. //Getting tag list. If empty, string "default" will be used.
  148. $return['tags'] = isset($return['tags'])?preg_split('#\W+#', $return['tags']):array('default');
  149. return $return;
  150. }
  151. }
  152. }
  153. /**
  154. * Getting tasklist.
  155. *
  156. * @return array List of command actions associated with {@link PHPDocCrontab} runner.
  157. */
  158. protected function prepareActions(){
  159. $actions = array();
  160. $Runner = $this->getCommandRunner();
  161. // Command loop
  162. foreach ($Runner->commands AS $command => $file){
  163. $CommandObject = $Runner->createCommand($command);
  164. if ($CommandObject instanceof $this) continue;
  165. $Reflection = new ReflectionObject($CommandObject);
  166. // Methods loop
  167. $Methods = $Reflection->getMethods(ReflectionMethod::IS_PUBLIC);
  168. foreach ($Methods AS $Method){
  169. $name = $Method->getName();
  170. //Filetring methods. Valid only public actions.
  171. if(
  172. !strncasecmp($name,'action',6) &&
  173. strlen($name) > 6 &&
  174. ($docComment = $this->parseDocComment($Method->getDocComment()))
  175. ){
  176. $name=substr($name, 6);
  177. $name[0]=strtolower($name[0]);
  178. $actions[] = array(
  179. 'command' => $command,
  180. 'action' => $name,
  181. 'docs' => $docComment
  182. );
  183. }
  184. }
  185. }
  186. return $actions;
  187. }
  188. /**
  189. * OS-independent background command execution .
  190. *
  191. * @param string $command
  192. * @param string $stdout path to file for writing stdout
  193. * @param string $stderr path to file for writing stderr
  194. */
  195. protected function runCommandBackground($command, $stdout, $stderr){
  196. $command =
  197. $this->interpreterPath.' '.
  198. $command.
  199. ' >'.escapeshellarg($stdout).
  200. ' 2>'.(($stdout === $stderr)?'&1':escapeshellarg($stderr));
  201. if ($this->isWindowsOS()){
  202. //Windows OS
  203. pclose(popen('start /B "Yii run command" '.$command, 'r'));
  204. }
  205. else{
  206. //nix based OS
  207. system($command.' &');
  208. }
  209. }
  210. /**
  211. * Checking is windows family OS
  212. *
  213. * @return boolean return true if script running under windows OS
  214. */
  215. protected function isWindowsOS(){
  216. return strncmp(PHP_OS, 'WIN', 3) === 0;
  217. }
  218. /**
  219. * Running actions associated with {@link PHPDocCrontab} runner and matched with timestamp.
  220. *
  221. * @param array $args List of run-tags to running actions (if empty, only "default" run-tag will be runned).
  222. */
  223. public function actionRun($args = array()){
  224. $tags = &$args;
  225. $tags[] = 'default';
  226. //Getting timestamp will be used as current
  227. $time = strtotime($this->timestamp);
  228. if ($time === false) throw new CException('Bad timestamp format');
  229. $now = explode(' ', date('i G j n w', $time));
  230. $runned = 0;
  231. foreach ($this->prepareActions() as $task) {
  232. if (array_intersect($tags, $task['docs']['tags'])){
  233. foreach ($now AS $key => $piece){
  234. //Checking current datetime on timestamp piece array.
  235. if (!in_array($piece, $task['docs'][0][$key])) continue 2;
  236. }
  237. //Forming command to run
  238. $command = $this->bootstrapScript.' '.$task['command'].' '.$task['action'];
  239. if (isset($task['docs']['args'])) $command .= ' '.escapeshellarg($task['docs']['args']);
  240. //Setting default stdout & stderr
  241. if (isset($task['docs']['stdout'])) $stdout = $task['docs']['stdout'];
  242. else $stdout = $this->logFileName;
  243. $stdout = $this->formatFileName($stdout, $task);
  244. $stderr = isset($task['docs']['stderr'])?$this->formatFileName($task['docs']['stderr'], $task):$stdout;
  245. $this->runCommandBackground($command, $stdout, $stderr);
  246. Yii::log('Running task ['.(++$runned).']: '.$task['command'].' '.$task['action'], CLogger::LEVEL_INFO, 'ext.'.__CLASS__);
  247. }
  248. }
  249. if ($runned > 0){
  250. Yii::log('Runned '.$runned.' task(s) at '.date('r', $time), CLogger::LEVEL_INFO, 'ext.'.__CLASS__);
  251. }
  252. else{
  253. Yii::log('No task on '.date('r', $time), CLogger::LEVEL_INFO, 'ext.'.__CLASS__);
  254. }
  255. }
  256. /**
  257. * Show actions associated with {@link PHPDocCrontab} runner.
  258. *
  259. * @param $args array List of run-tags for filtering action list (if empty, show all).
  260. */
  261. public function actionView($args = array()){
  262. $tags = &$args;
  263. foreach ($this->prepareActions() as $task) {
  264. if (!$tags || array_intersect($tags, $task['docs']['tags'])){
  265. //Forming to using with printf function
  266. $times = $task['docs']['_raw'];
  267. array_unshift($times, $task['command'].'.'.$task['action']);
  268. array_unshift($times, "Action %-40s on %6s %6s %6s %6s %6s %s\n");
  269. array_push($times, empty($task['docs']['tags'])?'':(' ('.implode(', ', $task['docs']['tags']).')'));
  270. call_user_func_array('printf', $times);
  271. }
  272. }
  273. }
  274. protected function formatFileName($pattern, $task){
  275. $pattern = str_replace(
  276. array('%L', '%C', '%A', '%P'),
  277. array($this->logsDir, $task['command'], $task['action'], getmypid()),
  278. $pattern
  279. );
  280. return preg_replace_callback('#%D\((.+)\)#U', create_function('$str', 'return date($str[1]);'), $pattern);
  281. }
  282. /**
  283. * Help command. Show command usage.
  284. */
  285. public function actionHelp(){
  286. echo $this->getHelp();
  287. }
  288. }