├── composer.json ├── phpspec.yml └── src ├── LockedCommandDecorator.php ├── SpecifiesLockName.php └── SpecifiesLockPath.php /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frankdejonge/locked-console-command", 3 | "description": "A symfony/console command decorator which locks.", 4 | "autoload": { 5 | "psr-4": { 6 | "FrankDeJonge\\LockedConsoleCommand\\": "src/" 7 | } 8 | }, 9 | "require": { 10 | "php": ">=5.5.9", 11 | "symfony/console": "~3.0", 12 | "symfony/filesystem": "~3.0" 13 | }, 14 | "require-dev": { 15 | "phpspec/phpspec": "~2.1", 16 | "henrikbjorn/phpspec-code-coverage": "~1.0" 17 | }, 18 | "license": "MIT", 19 | "authors": [ 20 | { 21 | "name": "Frank de Jonge", 22 | "email": "info@frenky.net" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /phpspec.yml: -------------------------------------------------------------------------------- 1 | suites: 2 | locked_command_suite: 3 | namespace: FrankDeJonge\LockedConsoleCommand 4 | psr4_prefix: FrankDeJonge\LockedConsoleCommand 5 | extensions: 6 | - PhpSpec\Extension\CodeCoverageExtension 7 | code_coverage: 8 | format: 9 | - html 10 | - text 11 | - clover 12 | output: 13 | html: coverage 14 | clover: coverage.xml 15 | -------------------------------------------------------------------------------- /src/LockedCommandDecorator.php: -------------------------------------------------------------------------------- 1 | decoratedCommand = $command; 40 | $this->setLockName($lockName); 41 | $this->lockPath = $lockPath; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function ignoreValidationErrors() 48 | { 49 | $this->decoratedCommand->ignoreValidationErrors(); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function setApplication(Application $application = null) 56 | { 57 | $this->decoratedCommand->setApplication($application); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function setHelperSet(HelperSet $helperSet) 64 | { 65 | $this->decoratedCommand->setHelperSet($helperSet); 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function getHelperSet() 72 | { 73 | return $this->decoratedCommand->getHelperSet(); 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | public function getApplication() 80 | { 81 | return $this->decoratedCommand->getApplication(); 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | public function isEnabled() 88 | { 89 | return $this->decoratedCommand->isEnabled(); 90 | } 91 | 92 | /** 93 | * @param string|LockHandler|null $lockName 94 | */ 95 | public function setLockName($lockName) 96 | { 97 | $this->lockName = $lockName; 98 | } 99 | 100 | /** 101 | * Runs the command. 102 | * 103 | * Before the decorated command is run, a lock is requested. 104 | * When failed to acquire the lock, the command exits. 105 | * 106 | * @param InputInterface $input An InputInterface instance 107 | * @param OutputInterface $output An OutputInterface instance 108 | * 109 | * @return int The command exit code 110 | */ 111 | public function run(InputInterface $input, OutputInterface $output) 112 | { 113 | $this->mergeApplicationDefinition(); 114 | $input->bind($this->getDefinition()); 115 | $lock = $this->getLockHandler($input); 116 | 117 | if ( ! $lock->lock()) { 118 | $this->writeLockedMessage($input, $output); 119 | 120 | return 1; 121 | } 122 | 123 | try { 124 | return $this->decoratedCommand->run($input, $output); 125 | } finally { 126 | $lock->release(); 127 | } 128 | } 129 | 130 | /** 131 | * {@inheritdoc} 132 | */ 133 | public function setCode(callable $code) 134 | { 135 | $this->decoratedCommand->setCode($code); 136 | 137 | return $this; 138 | } 139 | 140 | /** 141 | * {@inheritdoc} 142 | */ 143 | public function mergeApplicationDefinition($mergeArgs = true) 144 | { 145 | $this->decoratedCommand->mergeApplicationDefinition($mergeArgs); 146 | } 147 | 148 | /** 149 | * {@inheritdoc} 150 | */ 151 | public function setDefinition($definition) 152 | { 153 | $this->decoratedCommand->setDefinition($definition); 154 | 155 | return $this; 156 | } 157 | 158 | /** 159 | * {@inheritdoc} 160 | */ 161 | public function getDefinition() 162 | { 163 | return $this->decoratedCommand->getDefinition(); 164 | } 165 | 166 | /** 167 | * {@inheritdoc} 168 | */ 169 | public function getNativeDefinition() 170 | { 171 | return $this->decoratedCommand->getNativeDefinition(); 172 | } 173 | 174 | /** 175 | * {@inheritdoc} 176 | */ 177 | public function addArgument($name, $mode = null, $description = '', $default = null) 178 | { 179 | $this->decoratedCommand->addArgument($name, $mode, $description, $default); 180 | 181 | return $this; 182 | } 183 | 184 | /** 185 | * {@inheritdoc} 186 | */ 187 | public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) 188 | { 189 | $this->decoratedCommand->addOption($name, $shortcut, $mode, $description, $default); 190 | 191 | return $this; 192 | } 193 | 194 | /** 195 | * {@inheritdoc} 196 | */ 197 | public function setName($name) 198 | { 199 | $this->decoratedCommand->setName($name); 200 | 201 | return $this; 202 | } 203 | 204 | /** 205 | * {@inheritdoc} 206 | */ 207 | public function setProcessTitle($title) 208 | { 209 | $this->decoratedCommand->setProcessTitle($title); 210 | 211 | return $this; 212 | } 213 | 214 | /** 215 | * {@inheritdoc} 216 | */ 217 | public function getName() 218 | { 219 | return $this->decoratedCommand->getName(); 220 | } 221 | 222 | /** 223 | * {@inheritdoc} 224 | */ 225 | public function setDescription($description) 226 | { 227 | $this->decoratedCommand->setDescription($description); 228 | 229 | return $this; 230 | } 231 | 232 | /** 233 | * {@inheritdoc} 234 | */ 235 | public function getDescription() 236 | { 237 | return $this->decoratedCommand->getDescription(); 238 | } 239 | 240 | /** 241 | * {@inheritdoc} 242 | */ 243 | public function setHelp($help) 244 | { 245 | $this->decoratedCommand->setHelp($help); 246 | 247 | return $this; 248 | } 249 | 250 | /** 251 | * {@inheritdoc} 252 | */ 253 | public function getHelp() 254 | { 255 | return $this->decoratedCommand->getHelp(); 256 | } 257 | 258 | /** 259 | * {@inheritdoc} 260 | */ 261 | public function getProcessedHelp() 262 | { 263 | return $this->decoratedCommand->getProcessedHelp(); 264 | } 265 | 266 | /** 267 | * {@inheritdoc} 268 | */ 269 | public function setAliases($aliases) 270 | { 271 | $this->decoratedCommand->setAliases($aliases); 272 | 273 | return $this; 274 | } 275 | 276 | /** 277 | * {@inheritdoc} 278 | */ 279 | public function getAliases() 280 | { 281 | return $this->decoratedCommand->getAliases(); 282 | } 283 | 284 | /** 285 | * {@inheritdoc} 286 | */ 287 | public function getSynopsis($short = false) 288 | { 289 | return $this->decoratedCommand->getSynopsis($short); 290 | } 291 | 292 | /** 293 | * {@inheritdoc} 294 | */ 295 | public function getHelper($name) 296 | { 297 | return $this->decoratedCommand->getHelper($name); 298 | } 299 | 300 | /** 301 | * Get the locking helper. 302 | * 303 | * @param InputInterface $input 304 | * 305 | * @return LockHandler 306 | */ 307 | private function getLockHandler(InputInterface $input) 308 | { 309 | if ($this->lockName instanceof LockHandler) { 310 | return $this->lockName; 311 | } 312 | 313 | $lockName = $this->getLockName($input); 314 | $lockPath = $this->getLockPath($input); 315 | 316 | return new LockHandler($lockName, $lockPath); 317 | } 318 | 319 | /** 320 | * Get the name for the lock. 321 | * 322 | * @param InputInterface $input 323 | * 324 | * @return string 325 | */ 326 | public function getLockName(InputInterface $input) 327 | { 328 | if (is_string($this->lockName)) { 329 | return $this->lockName; 330 | } 331 | 332 | if ($this->lockName instanceof LockHandler) { 333 | return 'UNKNOWN'; 334 | } 335 | 336 | if ($this->decoratedCommand instanceof SpecifiesLockName) { 337 | return $this->decoratedCommand->getLockName($input); 338 | } 339 | 340 | return $this->decoratedCommand->getName(); 341 | } 342 | 343 | /** 344 | * Get the lock path. 345 | * 346 | * @param InputInterface $input 347 | * 348 | * @return null|string 349 | */ 350 | public function getLockPath(InputInterface $input) 351 | { 352 | if ($this->lockName instanceof LockHandler) { 353 | return 'UNKNOWN'; 354 | } 355 | 356 | if ($this->lockPath !== null) { 357 | return $this->lockPath; 358 | } 359 | 360 | if ($this->decoratedCommand instanceof SpecifiesLockPath) { 361 | return $this->decoratedCommand->getLockPath($input); 362 | } 363 | 364 | return sys_get_temp_dir(); 365 | } 366 | 367 | /** 368 | * Write the "is locked" message. 369 | * 370 | * @param InputInterface $input 371 | * @param OutputInterface $output 372 | */ 373 | private function writeLockedMessage(InputInterface $input, OutputInterface $output) 374 | { 375 | $commandName = $this->decoratedCommand->getName(); 376 | $lockName = $this->getLockName($input); 377 | $lockPath = $this->getLockPath($input); 378 | $message = sprintf( 379 | 'Command "%s" is already running, locked with "%s" at path "%s"', 380 | $commandName, 381 | $lockName, 382 | $lockPath 383 | ); 384 | 385 | if ($output instanceof ConsoleOutputInterface) { 386 | $output = $output->getErrorOutput(); 387 | } 388 | 389 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { 390 | $output->writeln($message); 391 | } 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /src/SpecifiesLockName.php: -------------------------------------------------------------------------------- 1 |