From ff4424a668252068ab22f56e4f4996196492be1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 21 Apr 2015 20:59:48 +0200 Subject: [PATCH] [Console] Bind the closure (code) to the Command if possible --- .../Component/Console/Command/Command.php | 7 ++++ .../Console/Tests/Command/CommandTest.php | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index 02e207eac775..0b968809938d 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -284,6 +284,13 @@ public function setCode($code) throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); } + if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + $code = \Closure::bind($code, $this); + } + } + $this->code = $code; return $this; diff --git a/src/Symfony/Component/Console/Tests/Command/CommandTest.php b/src/Symfony/Component/Console/Tests/Command/CommandTest.php index 08761163eb2d..42b2944ba77b 100644 --- a/src/Symfony/Component/Console/Tests/Command/CommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/CommandTest.php @@ -293,6 +293,33 @@ public function testSetCode() $this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay()); } + public function getSetCodeBindToClosureTests() + { + return array( + array(true, 'not bound to the command'), + array(false, 'bound to the command'), + ); + } + + /** @dataProvider getSetCodeBindToClosureTests */ + public function testSetCodeBindToClosure($previouslyBound, $expected) + { + if (PHP_VERSION_ID < 50400) { + $this->markTestSkipped('Test skipped, for PHP 5.4+ only.'); + } + + $code = createClosure(); + if ($previouslyBound) { + $code = $code->bindTo($this); + } + + $command = new \TestCommand(); + $command->setCode($code); + $tester = new CommandTester($command); + $tester->execute(array()); + $this->assertEquals('interact called'.PHP_EOL.$expected.PHP_EOL, $tester->getDisplay()); + } + public function testSetCodeWithNonClosureCallable() { $command = new \TestCommand(); @@ -346,3 +373,13 @@ public function testLegacyAsXml() $this->assertXmlStringEqualsXmlFile(self::$fixturesPath.'/command_asxml.txt', $command->asXml(), '->asXml() returns an XML representation of the command'); } } + +// In order to get an unbound closure, we should create it outside a class +// scope. +function createClosure() +{ + return function(InputInterface $input, OutputInterface $output) + { + $output->writeln($this instanceof Command ? 'bound to the command' : 'not bound to the command'); + }; +}