diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index 2922967b12..a97a4a7ad4 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * added `setInputs()` method to CommandTester for ease testing of commands expecting inputs * added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface) * added StreamableInputInterface +* added LockableTrait 3.1.0 ----- diff --git a/src/Symfony/Component/Console/Command/LockableTrait.php b/src/Symfony/Component/Console/Command/LockableTrait.php new file mode 100644 index 0000000000..30f8846768 --- /dev/null +++ b/src/Symfony/Component/Console/Command/LockableTrait.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Filesystem\LockHandler; + +/** + * Basic lock feature for commands. + * + * @author Fabien Potencier + */ +trait LockableTrait +{ + protected $lockHandler; + + /** + * Locks a command. + * + * @return bool + */ + protected function lock($name = null, $blocking = false) + { + if (!class_exists(LockHandler::class)) { + throw new RuntimeException('To enable the locking feature you must install the symfony/filesystem component.'); + } + + if (null !== $this->lockHandler) { + throw new LogicException('A lock is already in place.'); + } + + $this->lockHandler = new LockHandler($name ?: $this->getName()); + + if (!$this->lockHandler->lock($blocking)) { + $this->lockHandler = null; + + return false; + } + + return true; + } + + /** + * Releases the command lock if there is one. + */ + protected function release() + { + if ($this->lockHandler) { + $this->lockHandler->release(); + $this->lockHandler = null; + } + } +} diff --git a/src/Symfony/Component/Console/Tests/Command/LockableTraitTest.php b/src/Symfony/Component/Console/Tests/Command/LockableTraitTest.php new file mode 100644 index 0000000000..828ba163b6 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Command/LockableTraitTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Command; + +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Filesystem\LockHandler; + +class LockableTraitTest extends \PHPUnit_Framework_TestCase +{ + protected static $fixturesPath; + + public static function setUpBeforeClass() + { + self::$fixturesPath = __DIR__.'/../Fixtures/'; + require_once self::$fixturesPath.'/FooLockCommand.php'; + require_once self::$fixturesPath.'/FooLock2Command.php'; + } + + public function testLockIsReleased() + { + $command = new \FooLockCommand(); + + $tester = new CommandTester($command); + $this->assertSame(2, $tester->execute(array())); + $this->assertSame(2, $tester->execute(array())); + } + + public function testLockReturnsFalseIfAlreadyLockedByAnotherCommand() + { + $command = new \FooLockCommand(); + + $lock = new LockHandler($command->getName()); + $lock->lock(); + + $tester = new CommandTester($command); + $this->assertSame(1, $tester->execute(array())); + + $lock->release(); + $this->assertSame(2, $tester->execute(array())); + } + + public function testMultipleLockCallsThrowLogicException() + { + $command = new \FooLock2Command(); + + $tester = new CommandTester($command); + $this->assertSame(1, $tester->execute(array())); + } +} diff --git a/src/Symfony/Component/Console/Tests/Fixtures/FooLock2Command.php b/src/Symfony/Component/Console/Tests/Fixtures/FooLock2Command.php new file mode 100644 index 0000000000..4e4656f20f --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/FooLock2Command.php @@ -0,0 +1,28 @@ +setName('foo:lock2'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + try { + $this->lock(); + $this->lock(); + } catch (LogicException $e) { + return 1; + } + + return 2; + } +} diff --git a/src/Symfony/Component/Console/Tests/Fixtures/FooLockCommand.php b/src/Symfony/Component/Console/Tests/Fixtures/FooLockCommand.php new file mode 100644 index 0000000000..dfa28a6bec --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/FooLockCommand.php @@ -0,0 +1,27 @@ +setName('foo:lock'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + if (!$this->lock()) { + return 1; + } + + $this->release(); + + return 2; + } +} diff --git a/src/Symfony/Component/Console/composer.json b/src/Symfony/Component/Console/composer.json index cbd0b22d7d..cb73a00b8d 100644 --- a/src/Symfony/Component/Console/composer.json +++ b/src/Symfony/Component/Console/composer.json @@ -21,11 +21,13 @@ }, "require-dev": { "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/filesystem": "~2.8|~3.0", "symfony/process": "~2.8|~3.0", "psr/log": "~1.0" }, "suggest": { "symfony/event-dispatcher": "", + "symfony/filesystem": "", "symfony/process": "", "psr/log": "For using the console logger" },