8000 [Console] Add 'auto lockable' behaviour · symfony/symfony@3f9fc8a · GitHub
[go: up one dir, main page]

Skip to content

Commit 3f9fc8a

Browse files
[Console] Add 'auto lockable' behaviour
1 parent 45b557a commit 3f9fc8a

File tree

6 files changed

+177
-4
lines changed

6 files changed

+177
-4
lines changed

src/Symfony/Component/Console/Command/Command.php

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\Console\Helper\HelperSet;
2222
use Symfony\Component\Console\Exception\InvalidArgumentException;
2323
use Symfony\Component\Console\Exception\LogicException;
24+
use Symfony\Component\Filesystem\LockHandler;
2425

2526
/**
2627
* Base class for all commands.
@@ -43,6 +44,8 @@ class Command
4344
private $synopsis = array();
4445
private $usages = array();
4546
private $helperSet;
47+
private $autoLock = false;
48+
private $lock;
4649

4750
/**
4851
* Constructor.
@@ -250,12 +253,22 @@ public function run(InputInterface $input, OutputInterface $output)
250253

251254
$input->validate();
252255

253-
if ($this->code) {
254-
$statusCode = call_user_func($this->code, $input, $output);
255-
} else {
256-
$statusCode = $this->execute($input, $output);
256+
try {
257+
if (!$this->lock($output)) {
258+
$statusCode = 0;
259+
} elseif ($this->code) {
260+
$statusCode = call_user_func($this->code, $input, $output);
261+
} else {
262+
$statusCode = $this->execute($input, $output);
263+
}
264+
} catch (\Exception $e) {
265+
$this->release();
266+
267+
throw $e;
257268
}
258269

270+
$this->release();
271+
259272
return is_numeric($statusCode) ? (int) $statusCode : 0;
260273
}
261274

@@ -623,4 +636,60 @@ private function validateName($name)
623636
throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
624637
}
625638
}
639+
640+
/**
641+
* Gets whether or not the auto lock option is enabled.
642+
*
643+
* @return bool
644+
*/
645+
public function isAutoLock()
646+
{
647+
return $this->autoLock;
648+
}
649+
650+
/**
651+
* Sets the auto lock option.
652+
*
653+
* @param bool $autoLock
654+
*
655+
* @return Command The current instance
656+
*/
657+
public function setAutoLock($autoLock)
658+
{
659+
$this->autoLock = (boolean) $autoLock;
660+
}
661+
662+
/**
663+
* Puts a lock on the command.
664+
*
665+
* @param OutputInterface $output
666+
*
667+
* @return bool
668+
*/
669+
private function lock(OutputInterface $output)
670+
{
671+
if (!$this->isAutoLock()) {
672+
return true;
673+
}
674+
675+
$this->lock = new LockHandler($this->getName());
676+
677+
if (!$this->lock->lock()) {
678+
$output->writeln(sprintf('The command "%s" is already running in another process.', $this->getName()));
679+
680+
return false;
681+
}
682+
683+
return true;
684+
}
685+
686+
/**
687+
* Releases the command lock if there is one.
688+
*/
689+
private function release()
690+
{
691+
if ($this->lock) {
692+
$this->lock->release();
693+
}
694+
}
626695
}

src/Symfony/Component/Console/Tests/ApplicationTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public static function setUpBeforeClass()
4343
require_once self::$fixturesPath.'/Foo3Command.php';
4444
require_once self::$fixturesPath.'/Foo4Command.php';
4545
require_once self::$fixturesPath.'/Foo5Command.php';
46+
require_once self::$fixturesPath.'/FooAutoLock1Command.php';
47+
require_once self::$fixturesPath.'/FooAutoLock2Command.php';
4648
require_once self::$fixturesPath.'/FoobarCommand.php';
4749
require_once self::$fixturesPath.'/BarBucCommand.php';
4850
require_once self::$fixturesPath.'/FooSubnamespaced1Command.php';

src/Symfony/Component/Console/Tests/Command/CommandTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\Console\Output\OutputInterface;
2323
use Symfony\Component\Console\Output\NullOutput;
2424
use Symfony\Component\Console\Tester\CommandTester;
25+
use Symfony\Component\Filesystem\LockHandler;
2526

2627
class CommandTest extends \PHPUnit_Framework_TestCase
2728
{
@@ -338,6 +339,65 @@ public function callableMethodCommand(InputInterface $input, OutputInterface $ou
338339
{
339340
$output->writeln('from the code...');
340341
}
342+
343+
public function testIsAutoLock()
344+
{
345+
$command = new \Foo1Command();
346+
$this->assertFalse($command->isAutoLock());
347+
348+
$command = new \FooAutoLock1Command();
349+
$this->assertTrue($command->isAutoLock());
350+
}
351+
352+
public function testSetAutoLock()
353+
{
354+
$command = new \Foo1Command();
355+
$command->setAutoLock(true);
356+
357+
$this->assertTrue($command->isAutoLock());
358+
359+
$command->setAutoLock(false);
360+
361+
$this->assertFalse($command->isAutoLock());
362+
}
363+
364+
public function testCommandLock()
365+
{
366+
$command1 = new \FooAutoLock1Command();
367+
368+
$lock = new LockHandler($command1->getName());
369+
$lock->lock();
370+
371+
$tester = new CommandTester($command1);
372+
$tester->execute(array());
373+
$this->assertEquals('The command "foo:autolock1" is already running in another process.'.PHP_EOL, $tester->getDisplay());
374+
375+
$lock->release();
376+
}
377+
378+
public function testCommandLockIsReleased()
379+
{
380+
$command = new \FooAutoLock1Command();
381+
$tester = new CommandTester($command);
382+
$tester->execute(array());
383+
$tester->execute(array());
384+
$this->assertEquals('', $tester->getDisplay());
385+
}
386+
387+
public function testCommandLockIsReleasedAfterException()
388+
{
389+
$command = new \FooAutoLock2Command();
390+
$tester = new CommandTester($command);
391+
try {
392+
$tester->execute(array());
393+
} catch (\Exception $e) {
394+
}
395+
try {
396+
$tester->execute(array());
397+
} catch (\Exception $e) {
398+
}
399+
$this->assertEquals('', $tester->getDisplay());
400+
}
341401
}
342402

343403
// In order to get an unbound closure, we should create it outside a class
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
use Symfony\Component\Console\Command\Command;
4+
use Symfony\Component\Console\Input\InputInterface;
5+
use Symfony\Component\Console\Output\OutputInterface;
6+
7+
class FooAutoLock1Command extends Command
8+
{
9+
protected function configure()
10+
{
11+
$this
12+
->setName('foo:autolock1')
13+
->setAutoLock(true)
14+
;
15+
}
16+
17+
protected function execute(InputInterface $input, OutputInterface $output)
18+
{
19+
}
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
use Symfony\Component\Console\Command\Command;
4+
use Symfony\Component\Console\Input\InputInterface;
5+
use Symfony\Component\Console\Output\OutputInterface;
6+
7+
class FooAutoLock2Command extends Command
8+
{
9+
protected function configure()
10+
{
11+
$this
12+
->setName('foo:autolock2')
13+
->setAutoLock(true)
14+
;
15+
}
16+
17+
protected function execute(InputInterface $input, OutputInterface $output)
18+
{
19+
throw new \Exception();
20+
}
21+
}

src/Symfony/Component/Console/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"suggest": {
2828
"symfony/event-dispatcher": "",
2929
"symfony/process": "",
30+
"symfony/filesystem": "",
3031
"psr/log": "For using the console logger"
3132
},
3233
"autoload": {

0 commit comments

Comments
 (0)
0