diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index b5db6abecae29..d0ee13c444eb8 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGELOG * Add support for help definition via `AsCommand` attribute * Deprecate methods `Command::getDefaultName()` and `Command::getDefaultDescription()` in favor of the `#[AsCommand]` attribute * Add support for Markdown format in `Table` + * Add support for `LockableTrait` in invokable commands 7.2 --- diff --git a/src/Symfony/Component/Console/Command/LockableTrait.php b/src/Symfony/Component/Console/Command/LockableTrait.php index f0001cc52d772..b7abd2fdc5892 100644 --- a/src/Symfony/Component/Console/Command/LockableTrait.php +++ b/src/Symfony/Component/Console/Command/LockableTrait.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Console\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\LockInterface; @@ -48,10 +49,20 @@ private function lock(?string $name = null, bool $blocking = false): bool $store = new FlockStore(); } - $this->lockFactory = (new LockFactory($store)); + $this->lockFactory = new LockFactory($store); } - $this->lock = $this->lockFactory->createLock($name ?: $this->getName()); + if (!$name) { + if ($this instanceof Command) { + $name = $this->getName(); + } elseif ($attribute = (new \ReflectionClass($this::class))->getAttributes(AsCommand::class)) { + $name = $attribute[0]->newInstance()->name; + } else { + throw new LogicException(\sprintf('Lock name missing: provide it via "%s()", #[AsCommand] attribute, or by extending Command class.', __METHOD__)); + } + } + + $this->lock = $this->lockFactory->createLock($name); if (!$this->lock->acquire($blocking)) { $this->lock = null; diff --git a/src/Symfony/Component/Console/Tests/Command/LockableTraitTest.php b/src/Symfony/Component/Console/Tests/Command/LockableTraitTest.php index 0268d9681e5c5..3000906d7aab7 100644 --- a/src/Symfony/Component/Console/Tests/Command/LockableTraitTest.php +++ b/src/Symfony/Component/Console/Tests/Command/LockableTraitTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Console\Tests\Command; use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\SharedLockInterface; @@ -28,6 +29,7 @@ public static function setUpBeforeClass(): void require_once self::$fixturesPath.'/FooLockCommand.php'; require_once self::$fixturesPath.'/FooLock2Command.php'; require_once self::$fixturesPath.'/FooLock3Command.php'; + require_once self::$fixturesPath.'/FooLock4InvokableCommand.php'; } public function testLockIsReleased() @@ -80,4 +82,25 @@ public function testCustomLockFactoryIsUsed() $lockFactory->expects(static::once())->method('createLock')->willReturn($lock); $this->assertSame(1, $tester->execute([])); } + + public function testLockInvokableCommandReturnsFalseIfAlreadyLockedByAnotherCommand() + { + $command = new Command('foo:lock4'); + $command->setCode(new \FooLock4InvokableCommand()); + + if (SemaphoreStore::isSupported()) { + $store = new SemaphoreStore(); + } else { + $store = new FlockStore(); + } + + $lock = (new LockFactory($store))->createLock($command->getName()); + $lock->acquire(); + + $tester = new CommandTester($command); + $this->assertSame(Command::FAILURE, $tester->execute([])); + + $lock->release(); + $this->assertSame(Command::SUCCESS, $tester->execute([])); + } } diff --git a/src/Symfony/Component/Console/Tests/Fixtures/FooLock4InvokableCommand.php b/src/Symfony/Component/Console/Tests/Fixtures/FooLock4InvokableCommand.php new file mode 100644 index 0000000000000..7309234fa15ea --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/FooLock4InvokableCommand.php @@ -0,0 +1,22 @@ +lock()) { + return Command::FAILURE; + } + + $this->release(); + + return Command::SUCCESS; + } +}