AddConsoleCommandPassTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Console\Tests\DependencyInjection;
  11. use PHPUnit\Framework\TestCase;
  12. use Symfony\Component\Console\Command\Command;
  13. use Symfony\Component\Console\Command\LazyCommand;
  14. use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
  15. use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
  16. use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
  17. use Symfony\Component\DependencyInjection\ChildDefinition;
  18. use Symfony\Component\DependencyInjection\Compiler\PassConfig;
  19. use Symfony\Component\DependencyInjection\ContainerBuilder;
  20. use Symfony\Component\DependencyInjection\Definition;
  21. use Symfony\Component\DependencyInjection\Reference;
  22. use Symfony\Component\DependencyInjection\TypedReference;
  23. class AddConsoleCommandPassTest extends TestCase
  24. {
  25. /**
  26. * @dataProvider visibilityProvider
  27. */
  28. public function testProcess($public)
  29. {
  30. $container = new ContainerBuilder();
  31. $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
  32. $container->setParameter('my-command.class', 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand');
  33. $id = 'my-command';
  34. $definition = new Definition('%my-command.class%');
  35. $definition->setPublic($public);
  36. $definition->addTag('console.command');
  37. $container->setDefinition($id, $definition);
  38. $container->compile();
  39. $alias = 'console.command.public_alias.my-command';
  40. if ($public) {
  41. $this->assertFalse($container->hasAlias($alias));
  42. } else {
  43. // The alias is replaced by a Definition by the ReplaceAliasByActualDefinitionPass
  44. // in case the original service is private
  45. $this->assertFalse($container->hasDefinition($id));
  46. $this->assertTrue($container->hasDefinition($alias));
  47. }
  48. $this->assertTrue($container->hasParameter('console.command.ids'));
  49. $this->assertSame([$public ? $id : $alias], $container->getParameter('console.command.ids'));
  50. }
  51. public function testProcessRegistersLazyCommands()
  52. {
  53. $container = new ContainerBuilder();
  54. $command = $container
  55. ->register('my-command', MyCommand::class)
  56. ->setPublic(false)
  57. ->addTag('console.command', ['command' => 'my:command'])
  58. ->addTag('console.command', ['command' => 'my:alias'])
  59. ;
  60. (new AddConsoleCommandPass())->process($container);
  61. $commandLoader = $container->getDefinition('console.command_loader');
  62. $commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0));
  63. $this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass());
  64. $this->assertSame(['my:command' => 'my-command', 'my:alias' => 'my-command'], $commandLoader->getArgument(1));
  65. $this->assertEquals([['my-command' => new ServiceClosureArgument(new TypedReference('my-command', MyCommand::class))]], $commandLocator->getArguments());
  66. $this->assertSame([], $container->getParameter('console.command.ids'));
  67. $this->assertSame([['setName', ['my:command']], ['setAliases', [['my:alias']]]], $command->getMethodCalls());
  68. }
  69. public function testProcessFallsBackToDefaultName()
  70. {
  71. $container = new ContainerBuilder();
  72. $container
  73. ->register('with-default-name', NamedCommand::class)
  74. ->setPublic(false)
  75. ->addTag('console.command')
  76. ;
  77. $pass = new AddConsoleCommandPass();
  78. $pass->process($container);
  79. $commandLoader = $container->getDefinition('console.command_loader');
  80. $commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0));
  81. $this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass());
  82. $this->assertSame(['default' => 'with-default-name'], $commandLoader->getArgument(1));
  83. $this->assertEquals([['with-default-name' => new ServiceClosureArgument(new TypedReference('with-default-name', NamedCommand::class))]], $commandLocator->getArguments());
  84. $this->assertSame([], $container->getParameter('console.command.ids'));
  85. $container = new ContainerBuilder();
  86. $container
  87. ->register('with-default-name', NamedCommand::class)
  88. ->setPublic(false)
  89. ->addTag('console.command', ['command' => 'new-name'])
  90. ;
  91. $pass->process($container);
  92. $this->assertSame(['new-name' => 'with-default-name'], $container->getDefinition('console.command_loader')->getArgument(1));
  93. }
  94. public static function visibilityProvider()
  95. {
  96. return [
  97. [true],
  98. [false],
  99. ];
  100. }
  101. public function testProcessFallsBackToDefaultDescription()
  102. {
  103. $container = new ContainerBuilder();
  104. $container
  105. ->register('with-defaults', DescribedCommand::class)
  106. ->addTag('console.command')
  107. ;
  108. $pass = new AddConsoleCommandPass();
  109. $pass->process($container);
  110. $commandLoader = $container->getDefinition('console.command_loader');
  111. $commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0));
  112. $this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass());
  113. $this->assertSame(['cmdname' => 'with-defaults', 'cmdalias' => 'with-defaults'], $commandLoader->getArgument(1));
  114. $this->assertEquals([['with-defaults' => new ServiceClosureArgument(new Reference('.with-defaults.lazy'))]], $commandLocator->getArguments());
  115. $this->assertSame([], $container->getParameter('console.command.ids'));
  116. $initCounter = DescribedCommand::$initCounter;
  117. $command = $container->get('console.command_loader')->get('cmdname');
  118. $this->assertInstanceOf(LazyCommand::class, $command);
  119. $this->assertSame(['cmdalias'], $command->getAliases());
  120. $this->assertSame('Just testing', $command->getDescription());
  121. $this->assertTrue($command->isHidden());
  122. $this->assertTrue($command->isEnabled());
  123. $this->assertSame($initCounter, DescribedCommand::$initCounter);
  124. $this->assertSame('', $command->getHelp());
  125. $this->assertSame(1 + $initCounter, DescribedCommand::$initCounter);
  126. }
  127. public function testEscapesDefaultFromPhp()
  128. {
  129. $container = new ContainerBuilder();
  130. $container
  131. ->register('to-escape', EscapedDefaultsFromPhpCommand::class)
  132. ->addTag('console.command')
  133. ;
  134. $pass = new AddConsoleCommandPass();
  135. $pass->process($container);
  136. $commandLoader = $container->getDefinition('console.command_loader');
  137. $commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0));
  138. $this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass());
  139. $this->assertSame(['%%cmd%%' => 'to-escape', '%%cmdalias%%' => 'to-escape'], $commandLoader->getArgument(1));
  140. $this->assertEquals([['to-escape' => new ServiceClosureArgument(new Reference('.to-escape.lazy'))]], $commandLocator->getArguments());
  141. $this->assertSame([], $container->getParameter('console.command.ids'));
  142. $command = $container->get('console.command_loader')->get('%%cmd%%');
  143. $this->assertInstanceOf(LazyCommand::class, $command);
  144. $this->assertSame('%cmd%', $command->getName());
  145. $this->assertSame(['%cmdalias%'], $command->getAliases());
  146. $this->assertSame('Creates a 80% discount', $command->getDescription());
  147. }
  148. public function testProcessThrowAnExceptionIfTheServiceIsAbstract()
  149. {
  150. $this->expectException(\InvalidArgumentException::class);
  151. $this->expectExceptionMessage('The service "my-command" tagged "console.command" must not be abstract.');
  152. $container = new ContainerBuilder();
  153. $container->setResourceTracking(false);
  154. $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
  155. $definition = new Definition('Symfony\Component\Console\Tests\DependencyInjection\MyCommand');
  156. $definition->addTag('console.command');
  157. $definition->setAbstract(true);
  158. $container->setDefinition('my-command', $definition);
  159. $container->compile();
  160. }
  161. public function testProcessThrowAnExceptionIfTheServiceIsNotASubclassOfCommand()
  162. {
  163. $this->expectException(\InvalidArgumentException::class);
  164. $this->expectExceptionMessage('The service "my-command" tagged "console.command" must be a subclass of "Symfony\Component\Console\Command\Command".');
  165. $container = new ContainerBuilder();
  166. $container->setResourceTracking(false);
  167. $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
  168. $definition = new Definition('SplObjectStorage');
  169. $definition->addTag('console.command');
  170. $container->setDefinition('my-command', $definition);
  171. $container->compile();
  172. }
  173. public function testProcessPrivateServicesWithSameCommand()
  174. {
  175. $container = new ContainerBuilder();
  176. $className = 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand';
  177. $definition1 = new Definition($className);
  178. $definition1->addTag('console.command')->setPublic(false);
  179. $definition2 = new Definition($className);
  180. $definition2->addTag('console.command')->setPublic(false);
  181. $container->setDefinition('my-command1', $definition1);
  182. $container->setDefinition('my-command2', $definition2);
  183. (new AddConsoleCommandPass())->process($container);
  184. $aliasPrefix = 'console.command.public_alias.';
  185. $this->assertTrue($container->hasAlias($aliasPrefix.'my-command1'));
  186. $this->assertTrue($container->hasAlias($aliasPrefix.'my-command2'));
  187. }
  188. public function testProcessOnChildDefinitionWithClass()
  189. {
  190. $container = new ContainerBuilder();
  191. $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
  192. $className = 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand';
  193. $parentId = 'my-parent-command';
  194. $childId = 'my-child-command';
  195. $parentDefinition = new Definition(/* no class */);
  196. $parentDefinition->setAbstract(true)->setPublic(false);
  197. $childDefinition = new ChildDefinition($parentId);
  198. $childDefinition->addTag('console.command')->setPublic(true);
  199. $childDefinition->setClass($className);
  200. $container->setDefinition($parentId, $parentDefinition);
  201. $container->setDefinition($childId, $childDefinition);
  202. $container->compile();
  203. $command = $container->get($childId);
  204. $this->assertInstanceOf($className, $command);
  205. }
  206. public function testProcessOnChildDefinitionWithParentClass()
  207. {
  208. $container = new ContainerBuilder();
  209. $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
  210. $className = 'Symfony\Component\Console\Tests\DependencyInjection\MyCommand';
  211. $parentId = 'my-parent-command';
  212. $childId = 'my-child-command';
  213. $parentDefinition = new Definition($className);
  214. $parentDefinition->setAbstract(true)->setPublic(false);
  215. $childDefinition = new ChildDefinition($parentId);
  216. $childDefinition->addTag('console.command')->setPublic(true);
  217. $container->setDefinition($parentId, $parentDefinition);
  218. $container->setDefinition($childId, $childDefinition);
  219. $container->compile();
  220. $command = $container->get($childId);
  221. $this->assertInstanceOf($className, $command);
  222. }
  223. public function testProcessOnChildDefinitionWithoutClass()
  224. {
  225. $this->expectException(\RuntimeException::class);
  226. $this->expectExceptionMessage('The definition for "my-child-command" has no class.');
  227. $container = new ContainerBuilder();
  228. $container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
  229. $parentId = 'my-parent-command';
  230. $childId = 'my-child-command';
  231. $parentDefinition = new Definition();
  232. $parentDefinition->setAbstract(true)->setPublic(false);
  233. $childDefinition = new ChildDefinition($parentId);
  234. $childDefinition->addTag('console.command')->setPublic(true);
  235. $container->setDefinition($parentId, $parentDefinition);
  236. $container->setDefinition($childId, $childDefinition);
  237. $container->compile();
  238. }
  239. }
  240. class MyCommand extends Command
  241. {
  242. }
  243. class NamedCommand extends Command
  244. {
  245. protected static $defaultName = 'default';
  246. }
  247. class EscapedDefaultsFromPhpCommand extends Command
  248. {
  249. protected static $defaultName = '%cmd%|%cmdalias%';
  250. protected static $defaultDescription = 'Creates a 80% discount';
  251. }
  252. class DescribedCommand extends Command
  253. {
  254. public static $initCounter = 0;
  255. protected static $defaultName = '|cmdname|cmdalias';
  256. protected static $defaultDescription = 'Just testing';
  257. public function __construct()
  258. {
  259. ++self::$initCounter;
  260. parent::__construct();
  261. }
  262. }