AutowirePassTest.php 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176
  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\DependencyInjection\Tests\Compiler;
  11. use PHPUnit\Framework\TestCase;
  12. use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
  13. use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
  14. use Symfony\Component\DependencyInjection\ContainerBuilder;
  15. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  16. use Symfony\Component\DependencyInjection\Reference;
  17. use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic;
  18. use Symfony\Component\DependencyInjection\TypedReference;
  19. /**
  20. * @autor ThurData <info@thurdata.ch>
  21. */
  22. class AutowirePassTest extends TestCase
  23. {
  24. public function testProcess()
  25. {
  26. $container = new ContainerBuilder();
  27. $container->register(Foo::class);
  28. $barDefinition = $container->register('bar', __NAMESPACE__.'\Bar');
  29. $barDefinition->setAutowired(true);
  30. (new ResolveClassPass())->process($container);
  31. (new AutowirePass())->process($container);
  32. $this->assertCount(1, $container->getDefinition('bar')->getArguments());
  33. $this->assertEquals(Foo::class, (string) $container->getDefinition('bar')->getArgument(0));
  34. }
  35. /**
  36. * @requires PHP 5.6
  37. */
  38. public function testProcessVariadic()
  39. {
  40. $container = new ContainerBuilder();
  41. $container->register(Foo::class);
  42. $definition = $container->register('fooVariadic', FooVariadic::class);
  43. $definition->setAutowired(true);
  44. (new ResolveClassPass())->process($container);
  45. (new AutowirePass())->process($container);
  46. $this->assertCount(1, $container->getDefinition('fooVariadic')->getArguments());
  47. $this->assertEquals(Foo::class, (string) $container->getDefinition('fooVariadic')->getArgument(0));
  48. }
  49. /**
  50. * @group legacy
  51. * @expectedDeprecation Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. You should alias the "Symfony\Component\DependencyInjection\Tests\Compiler\B" service to "Symfony\Component\DependencyInjection\Tests\Compiler\A" instead.
  52. * @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\RuntimeException
  53. * @expectedExceptionMessageInSymfony4 Cannot autowire service "c": argument "$a" of method "Symfony\Component\DependencyInjection\Tests\Compiler\C::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\A" but no such service exists. You should maybe alias this class to the existing "Symfony\Component\DependencyInjection\Tests\Compiler\B" service.
  54. */
  55. public function testProcessAutowireParent()
  56. {
  57. $container = new ContainerBuilder();
  58. $container->register(B::class);
  59. $cDefinition = $container->register('c', __NAMESPACE__.'\C');
  60. $cDefinition->setAutowired(true);
  61. (new ResolveClassPass())->process($container);
  62. (new AutowirePass())->process($container);
  63. $this->assertCount(1, $container->getDefinition('c')->getArguments());
  64. $this->assertEquals(B::class, (string) $container->getDefinition('c')->getArgument(0));
  65. }
  66. /**
  67. * @group legacy
  68. * @expectedDeprecation Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. Try changing the type-hint for argument "$a" of method "Symfony\Component\DependencyInjection\Tests\Compiler\C::__construct()" to "Symfony\Component\DependencyInjection\Tests\Compiler\AInterface" instead.
  69. * @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\RuntimeException
  70. * @expectedExceptionMessageInSymfony4 Cannot autowire service "c": argument "$a" of method "Symfony\Component\DependencyInjection\Tests\Compiler\C::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\A" but no such service exists. You should maybe alias this class to the existing "Symfony\Component\DependencyInjection\Tests\Compiler\B" service.
  71. */
  72. public function testProcessLegacyAutowireWithAvailableInterface()
  73. {
  74. $container = new ContainerBuilder();
  75. $container->setAlias(AInterface::class, B::class);
  76. $container->register(B::class);
  77. $cDefinition = $container->register('c', __NAMESPACE__.'\C');
  78. $cDefinition->setAutowired(true);
  79. (new ResolveClassPass())->process($container);
  80. (new AutowirePass())->process($container);
  81. $this->assertCount(1, $container->getDefinition('c')->getArguments());
  82. $this->assertEquals(B::class, (string) $container->getDefinition('c')->getArgument(0));
  83. }
  84. /**
  85. * @group legacy
  86. * @expectedDeprecation Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. You should alias the "Symfony\Component\DependencyInjection\Tests\Compiler\F" service to "Symfony\Component\DependencyInjection\Tests\Compiler\DInterface" instead.
  87. * @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\RuntimeException
  88. * @expectedExceptionMessageInSymfony4 Cannot autowire service "g": argument "$d" of method "Symfony\Component\DependencyInjection\Tests\Compiler\G::__construct()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\DInterface" but no such service exists. You should maybe alias this interface to the existing "Symfony\Component\DependencyInjection\Tests\Compiler\F" service.
  89. */
  90. public function testProcessAutowireInterface()
  91. {
  92. $container = new ContainerBuilder();
  93. $container->register(F::class);
  94. $gDefinition = $container->register('g', __NAMESPACE__.'\G');
  95. $gDefinition->setAutowired(true);
  96. (new ResolveClassPass())->process($container);
  97. (new AutowirePass())->process($container);
  98. $this->assertCount(3, $container->getDefinition('g')->getArguments());
  99. $this->assertEquals(F::class, (string) $container->getDefinition('g')->getArgument(0));
  100. $this->assertEquals(F::class, (string) $container->getDefinition('g')->getArgument(1));
  101. $this->assertEquals(F::class, (string) $container->getDefinition('g')->getArgument(2));
  102. }
  103. public function testCompleteExistingDefinition()
  104. {
  105. $container = new ContainerBuilder();
  106. $container->register('b', __NAMESPACE__.'\B');
  107. $container->register(DInterface::class, F::class);
  108. $hDefinition = $container->register('h', __NAMESPACE__.'\H')->addArgument(new Reference('b'));
  109. $hDefinition->setAutowired(true);
  110. (new ResolveClassPass())->process($container);
  111. (new AutowirePass())->process($container);
  112. $this->assertCount(2, $container->getDefinition('h')->getArguments());
  113. $this->assertEquals('b', (string) $container->getDefinition('h')->getArgument(0));
  114. $this->assertEquals(DInterface::class, (string) $container->getDefinition('h')->getArgument(1));
  115. }
  116. public function testCompleteExistingDefinitionWithNotDefinedArguments()
  117. {
  118. $container = new ContainerBuilder();
  119. $container->register(B::class);
  120. $container->register(DInterface::class, F::class);
  121. $hDefinition = $container->register('h', __NAMESPACE__.'\H')->addArgument('')->addArgument('');
  122. $hDefinition->setAutowired(true);
  123. (new ResolveClassPass())->process($container);
  124. (new AutowirePass())->process($container);
  125. $this->assertCount(2, $container->getDefinition('h')->getArguments());
  126. $this->assertEquals(B::class, (string) $container->getDefinition('h')->getArgument(0));
  127. $this->assertEquals(DInterface::class, (string) $container->getDefinition('h')->getArgument(1));
  128. }
  129. public function testExceptionsAreStored()
  130. {
  131. $container = new ContainerBuilder();
  132. $container->register('c1', __NAMESPACE__.'\CollisionA');
  133. $container->register('c2', __NAMESPACE__.'\CollisionB');
  134. $container->register('c3', __NAMESPACE__.'\CollisionB');
  135. $aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired');
  136. $aDefinition->setAutowired(true);
  137. $pass = new AutowirePass(false);
  138. $pass->process($container);
  139. $this->assertCount(1, $pass->getAutowiringExceptions());
  140. }
  141. /**
  142. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  143. * @expectedExceptionMessage Unable to resolve service "private_service": constructor of class "Symfony\Component\DependencyInjection\Tests\Compiler\PrivateConstructor" must be public.
  144. */
  145. public function testPrivateConstructorThrowsAutowireException()
  146. {
  147. $container = new ContainerBuilder();
  148. $container->autowire('private_service', __NAMESPACE__.'\PrivateConstructor');
  149. $pass = new AutowirePass(true);
  150. $pass->process($container);
  151. }
  152. /**
  153. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  154. * @expectedExceptionMessage Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\CannotBeAutowired::__construct()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "c1", "c2", "c3".
  155. */
  156. public function testTypeCollision()
  157. {
  158. $container = new ContainerBuilder();
  159. $container->register('c1', __NAMESPACE__.'\CollisionA');
  160. $container->register('c2', __NAMESPACE__.'\CollisionB');
  161. $container->register('c3', __NAMESPACE__.'\CollisionB');
  162. $aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired');
  163. $aDefinition->setAutowired(true);
  164. $pass = new AutowirePass();
  165. $pass->process($container);
  166. }
  167. /**
  168. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  169. * @expectedExceptionMessage Cannot autowire service "a": argument "$k" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotGuessableArgument::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" but no such service exists. You should maybe alias this class to one of these existing services: "a1", "a2".
  170. */
  171. public function testTypeNotGuessable()
  172. {
  173. $container = new ContainerBuilder();
  174. $container->register('a1', __NAMESPACE__.'\Foo');
  175. $container->register('a2', __NAMESPACE__.'\Foo');
  176. $aDefinition = $container->register('a', __NAMESPACE__.'\NotGuessableArgument');
  177. $aDefinition->setAutowired(true);
  178. $pass = new AutowirePass();
  179. $pass->process($container);
  180. }
  181. /**
  182. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  183. * @expectedExceptionMessage Cannot autowire service "a": argument "$k" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotGuessableArgumentForSubclass::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\A" but no such service exists. You should maybe alias this class to one of these existing services: "a1", "a2".
  184. */
  185. public function testTypeNotGuessableWithSubclass()
  186. {
  187. $container = new ContainerBuilder();
  188. $container->register('a1', __NAMESPACE__.'\B');
  189. $container->register('a2', __NAMESPACE__.'\B');
  190. $aDefinition = $container->register('a', __NAMESPACE__.'\NotGuessableArgumentForSubclass');
  191. $aDefinition->setAutowired(true);
  192. $pass = new AutowirePass();
  193. $pass->process($container);
  194. }
  195. /**
  196. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  197. * @expectedExceptionMessage Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\CannotBeAutowired::__construct()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" but no such service exists.
  198. */
  199. public function testTypeNotGuessableNoServicesFound()
  200. {
  201. $container = new ContainerBuilder();
  202. $aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired');
  203. $aDefinition->setAutowired(true);
  204. $pass = new AutowirePass();
  205. $pass->process($container);
  206. }
  207. public function testTypeNotGuessableWithTypeSet()
  208. {
  209. $container = new ContainerBuilder();
  210. $container->register('a1', __NAMESPACE__.'\Foo');
  211. $container->register('a2', __NAMESPACE__.'\Foo');
  212. $container->register(Foo::class, Foo::class);
  213. $aDefinition = $container->register('a', __NAMESPACE__.'\NotGuessableArgument');
  214. $aDefinition->setAutowired(true);
  215. $pass = new AutowirePass();
  216. $pass->process($container);
  217. $this->assertCount(1, $container->getDefinition('a')->getArguments());
  218. $this->assertEquals(Foo::class, (string) $container->getDefinition('a')->getArgument(0));
  219. }
  220. public function testWithTypeSet()
  221. {
  222. $container = new ContainerBuilder();
  223. $container->register('c1', __NAMESPACE__.'\CollisionA');
  224. $container->register('c2', __NAMESPACE__.'\CollisionB');
  225. $container->setAlias(CollisionInterface::class, 'c2');
  226. $aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired');
  227. $aDefinition->setAutowired(true);
  228. $pass = new AutowirePass();
  229. $pass->process($container);
  230. $this->assertCount(1, $container->getDefinition('a')->getArguments());
  231. $this->assertEquals(CollisionInterface::class, (string) $container->getDefinition('a')->getArgument(0));
  232. }
  233. public function testCreateDefinition()
  234. {
  235. $container = new ContainerBuilder();
  236. $coopTilleulsDefinition = $container->register('coop_tilleuls', __NAMESPACE__.'\LesTilleuls');
  237. $coopTilleulsDefinition->setAutowired(true);
  238. $pass = new AutowirePass();
  239. $pass->process($container);
  240. $this->assertCount(2, $container->getDefinition('coop_tilleuls')->getArguments());
  241. $this->assertEquals('autowired.Symfony\Component\DependencyInjection\Tests\Compiler\Dunglas', $container->getDefinition('coop_tilleuls')->getArgument(0));
  242. $this->assertEquals('autowired.Symfony\Component\DependencyInjection\Tests\Compiler\Dunglas', $container->getDefinition('coop_tilleuls')->getArgument(1));
  243. $dunglasDefinition = $container->getDefinition('autowired.Symfony\Component\DependencyInjection\Tests\Compiler\Dunglas');
  244. $this->assertEquals(__NAMESPACE__.'\Dunglas', $dunglasDefinition->getClass());
  245. $this->assertFalse($dunglasDefinition->isPublic());
  246. $this->assertCount(1, $dunglasDefinition->getArguments());
  247. $this->assertEquals('autowired.Symfony\Component\DependencyInjection\Tests\Compiler\Lille', $dunglasDefinition->getArgument(0));
  248. $lilleDefinition = $container->getDefinition('autowired.Symfony\Component\DependencyInjection\Tests\Compiler\Lille');
  249. $this->assertEquals(__NAMESPACE__.'\Lille', $lilleDefinition->getClass());
  250. }
  251. public function testResolveParameter()
  252. {
  253. $container = new ContainerBuilder();
  254. $container->setParameter('class_name', Bar::class);
  255. $container->register(Foo::class);
  256. $barDefinition = $container->register('bar', '%class_name%');
  257. $barDefinition->setAutowired(true);
  258. (new ResolveClassPass())->process($container);
  259. (new AutowirePass())->process($container);
  260. $this->assertEquals(Foo::class, $container->getDefinition('bar')->getArgument(0));
  261. }
  262. public function testOptionalParameter()
  263. {
  264. $container = new ContainerBuilder();
  265. $container->register(A::class);
  266. $container->register(Foo::class);
  267. $optDefinition = $container->register('opt', __NAMESPACE__.'\OptionalParameter');
  268. $optDefinition->setAutowired(true);
  269. (new ResolveClassPass())->process($container);
  270. (new AutowirePass())->process($container);
  271. $definition = $container->getDefinition('opt');
  272. $this->assertNull($definition->getArgument(0));
  273. $this->assertEquals(A::class, $definition->getArgument(1));
  274. $this->assertEquals(Foo::class, $definition->getArgument(2));
  275. }
  276. public function testDontTriggerAutowiring()
  277. {
  278. $container = new ContainerBuilder();
  279. $container->register(Foo::class);
  280. $container->register('bar', __NAMESPACE__.'\Bar');
  281. (new ResolveClassPass())->process($container);
  282. (new AutowirePass())->process($container);
  283. $this->assertCount(0, $container->getDefinition('bar')->getArguments());
  284. }
  285. /**
  286. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  287. * @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class was not found.
  288. */
  289. public function testClassNotFoundThrowsException()
  290. {
  291. $container = new ContainerBuilder();
  292. $aDefinition = $container->register('a', __NAMESPACE__.'\BadTypeHintedArgument');
  293. $aDefinition->setAutowired(true);
  294. $pass = new AutowirePass();
  295. $pass->process($container);
  296. }
  297. /**
  298. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  299. * @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass" but this class is missing a parent class (Class Symfony\Bug\NotExistClass not found).
  300. */
  301. public function testParentClassNotFoundThrowsException()
  302. {
  303. $container = new ContainerBuilder();
  304. $aDefinition = $container->register('a', __NAMESPACE__.'\BadParentTypeHintedArgument');
  305. $aDefinition->setAutowired(true);
  306. $pass = new AutowirePass();
  307. $pass->process($container);
  308. }
  309. /**
  310. * @group legacy
  311. * @expectedDeprecation Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. You should rename (or alias) the "foo" service to "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" instead.
  312. * @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  313. * @expectedExceptionMessageInSymfony4 Cannot autowire service "bar": argument "$foo" of method "Symfony\Component\DependencyInjection\Tests\Compiler\Bar::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" but this service is abstract. You should maybe alias this class to the existing "foo" service.
  314. */
  315. public function testDontUseAbstractServices()
  316. {
  317. $container = new ContainerBuilder();
  318. $container->register(Foo::class)->setAbstract(true);
  319. $container->register('foo', __NAMESPACE__.'\Foo');
  320. $container->register('bar', __NAMESPACE__.'\Bar')->setAutowired(true);
  321. (new ResolveClassPass())->process($container);
  322. (new AutowirePass())->process($container);
  323. }
  324. public function testSomeSpecificArgumentsAreSet()
  325. {
  326. $container = new ContainerBuilder();
  327. $container->register('foo', Foo::class);
  328. $container->register(A::class);
  329. $container->register(Dunglas::class);
  330. $container->register('multiple', __NAMESPACE__.'\MultipleArguments')
  331. ->setAutowired(true)
  332. // set the 2nd (index 1) argument only: autowire the first and third
  333. // args are: A, Foo, Dunglas
  334. ->setArguments(array(
  335. 1 => new Reference('foo'),
  336. ));
  337. (new ResolveClassPass())->process($container);
  338. (new AutowirePass())->process($container);
  339. $definition = $container->getDefinition('multiple');
  340. $this->assertEquals(
  341. array(
  342. new TypedReference(A::class, A::class, MultipleArguments::class),
  343. new Reference('foo'),
  344. new TypedReference(Dunglas::class, Dunglas::class, MultipleArguments::class),
  345. ),
  346. $definition->getArguments()
  347. );
  348. }
  349. /**
  350. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  351. * @expectedExceptionMessage Cannot autowire service "arg_no_type_hint": argument "$foo" of method "Symfony\Component\DependencyInjection\Tests\Compiler\MultipleArguments::__construct()" must have a type-hint or be given a value explicitly.
  352. */
  353. public function testScalarArgsCannotBeAutowired()
  354. {
  355. $container = new ContainerBuilder();
  356. $container->register(A::class);
  357. $container->register(Dunglas::class);
  358. $container->register('arg_no_type_hint', __NAMESPACE__.'\MultipleArguments')
  359. ->setAutowired(true);
  360. (new ResolveClassPass())->process($container);
  361. (new AutowirePass())->process($container);
  362. }
  363. public function testOptionalScalarNotReallyOptionalUsesDefaultValue()
  364. {
  365. $container = new ContainerBuilder();
  366. $container->register(A::class);
  367. $container->register(Lille::class);
  368. $definition = $container->register('not_really_optional_scalar', __NAMESPACE__.'\MultipleArgumentsOptionalScalarNotReallyOptional')
  369. ->setAutowired(true);
  370. (new ResolveClassPass())->process($container);
  371. (new AutowirePass())->process($container);
  372. $this->assertSame('default_val', $definition->getArgument(1));
  373. }
  374. public function testOptionalScalarArgsDontMessUpOrder()
  375. {
  376. $container = new ContainerBuilder();
  377. $container->register(A::class);
  378. $container->register(Lille::class);
  379. $container->register('with_optional_scalar', __NAMESPACE__.'\MultipleArgumentsOptionalScalar')
  380. ->setAutowired(true);
  381. (new ResolveClassPass())->process($container);
  382. (new AutowirePass())->process($container);
  383. $definition = $container->getDefinition('with_optional_scalar');
  384. $this->assertEquals(
  385. array(
  386. new TypedReference(A::class, A::class, MultipleArgumentsOptionalScalar::class),
  387. // use the default value
  388. 'default_val',
  389. new TypedReference(Lille::class, Lille::class),
  390. ),
  391. $definition->getArguments()
  392. );
  393. }
  394. public function testOptionalScalarArgsNotPassedIfLast()
  395. {
  396. $container = new ContainerBuilder();
  397. $container->register(A::class);
  398. $container->register(Lille::class);
  399. $container->register('with_optional_scalar_last', __NAMESPACE__.'\MultipleArgumentsOptionalScalarLast')
  400. ->setAutowired(true);
  401. (new ResolveClassPass())->process($container);
  402. (new AutowirePass())->process($container);
  403. $definition = $container->getDefinition('with_optional_scalar_last');
  404. $this->assertEquals(
  405. array(
  406. new TypedReference(A::class, A::class, MultipleArgumentsOptionalScalarLast::class),
  407. new TypedReference(Lille::class, Lille::class, MultipleArgumentsOptionalScalarLast::class),
  408. ),
  409. $definition->getArguments()
  410. );
  411. }
  412. public function testOptionalArgsNoRequiredForCoreClasses()
  413. {
  414. $container = new ContainerBuilder();
  415. $container->register('foo', \SplFileObject::class)
  416. ->addArgument('foo.txt')
  417. ->setAutowired(true);
  418. (new AutowirePass())->process($container);
  419. $definition = $container->getDefinition('foo');
  420. $this->assertEquals(
  421. array('foo.txt'),
  422. $definition->getArguments()
  423. );
  424. }
  425. public function testSetterInjection()
  426. {
  427. $container = new ContainerBuilder();
  428. $container->register(Foo::class);
  429. $container->register(A::class);
  430. $container->register(CollisionA::class);
  431. $container->register(CollisionB::class);
  432. // manually configure *one* call, to override autowiring
  433. $container
  434. ->register('setter_injection', SetterInjection::class)
  435. ->setAutowired(true)
  436. ->addMethodCall('setWithCallsConfigured', array('manual_arg1', 'manual_arg2'))
  437. ;
  438. (new ResolveClassPass())->process($container);
  439. (new AutowirePass())->process($container);
  440. $methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
  441. $this->assertEquals(
  442. array('setWithCallsConfigured', 'setFoo', 'setDependencies', 'setChildMethodWithoutDocBlock'),
  443. array_column($methodCalls, 0)
  444. );
  445. // test setWithCallsConfigured args
  446. $this->assertEquals(
  447. array('manual_arg1', 'manual_arg2'),
  448. $methodCalls[0][1]
  449. );
  450. // test setFoo args
  451. $this->assertEquals(
  452. array(new TypedReference(Foo::class, Foo::class, SetterInjection::class)),
  453. $methodCalls[1][1]
  454. );
  455. }
  456. public function testExplicitMethodInjection()
  457. {
  458. $container = new ContainerBuilder();
  459. $container->register(Foo::class);
  460. $container->register(A::class);
  461. $container->register(CollisionA::class);
  462. $container->register(CollisionB::class);
  463. $container
  464. ->register('setter_injection', SetterInjection::class)
  465. ->setAutowired(true)
  466. ->addMethodCall('notASetter', array())
  467. ;
  468. (new ResolveClassPass())->process($container);
  469. (new AutowirePass())->process($container);
  470. $methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
  471. $this->assertEquals(
  472. array('notASetter', 'setFoo', 'setDependencies', 'setWithCallsConfigured', 'setChildMethodWithoutDocBlock'),
  473. array_column($methodCalls, 0)
  474. );
  475. $this->assertEquals(
  476. array(new TypedReference(A::class, A::class, SetterInjection::class)),
  477. $methodCalls[0][1]
  478. );
  479. }
  480. public function testTypedReference()
  481. {
  482. $container = new ContainerBuilder();
  483. $container
  484. ->register('bar', Bar::class)
  485. ->setProperty('a', array(new TypedReference(A::class, A::class, Bar::class)))
  486. ;
  487. $pass = new AutowirePass();
  488. $pass->process($container);
  489. $this->assertSame(A::class, $container->getDefinition('autowired.'.A::class)->getClass());
  490. }
  491. /**
  492. * @dataProvider getCreateResourceTests
  493. * @group legacy
  494. */
  495. public function testCreateResourceForClass($className, $isEqual)
  496. {
  497. $startingResource = AutowirePass::createResourceForClass(
  498. new \ReflectionClass(__NAMESPACE__.'\ClassForResource')
  499. );
  500. $newResource = AutowirePass::createResourceForClass(
  501. new \ReflectionClass(__NAMESPACE__.'\\'.$className)
  502. );
  503. // hack so the objects don't differ by the class name
  504. $startingReflObject = new \ReflectionObject($startingResource);
  505. $reflProp = $startingReflObject->getProperty('class');
  506. $reflProp->setAccessible(true);
  507. $reflProp->setValue($startingResource, __NAMESPACE__.'\\'.$className);
  508. if ($isEqual) {
  509. $this->assertEquals($startingResource, $newResource);
  510. } else {
  511. $this->assertNotEquals($startingResource, $newResource);
  512. }
  513. }
  514. public function getCreateResourceTests()
  515. {
  516. return array(
  517. array('IdenticalClassResource', true),
  518. array('ClassChangedConstructorArgs', false),
  519. );
  520. }
  521. public function testIgnoreServiceWithClassNotExisting()
  522. {
  523. $container = new ContainerBuilder();
  524. $container->register('class_not_exist', __NAMESPACE__.'\OptionalServiceClass');
  525. $barDefinition = $container->register('bar', __NAMESPACE__.'\Bar');
  526. $barDefinition->setAutowired(true);
  527. $pass = new AutowirePass();
  528. $pass->process($container);
  529. $this->assertTrue($container->hasDefinition('bar'));
  530. }
  531. /**
  532. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  533. * @expectedExceptionMessage Cannot autowire service "setter_injection_collision": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\SetterInjectionCollision::setMultipleInstancesForOneArg()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "c1", "c2".
  534. */
  535. public function testSetterInjectionCollisionThrowsException()
  536. {
  537. $container = new ContainerBuilder();
  538. $container->register('c1', CollisionA::class);
  539. $container->register('c2', CollisionB::class);
  540. $aDefinition = $container->register('setter_injection_collision', SetterInjectionCollision::class);
  541. $aDefinition->setAutowired(true);
  542. $pass = new AutowirePass();
  543. $pass->process($container);
  544. }
  545. /**
  546. * @group legacy
  547. * @expectedDeprecation Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. You should rename (or alias) the "foo" service to "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" instead.
  548. * @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  549. * @expectedExceptionMessageInSymfony4 Cannot autowire service "bar": argument "$foo" of method "Symfony\Component\DependencyInjection\Tests\Compiler\Bar::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" but no such service exists. You should maybe alias this class to the existing "foo" service.
  550. */
  551. public function testProcessDoesNotTriggerDeprecations()
  552. {
  553. $container = new ContainerBuilder();
  554. $container->register('deprecated', 'Symfony\Component\DependencyInjection\Tests\Fixtures\DeprecatedClass')->setDeprecated(true);
  555. $container->register('foo', __NAMESPACE__.'\Foo');
  556. $container->register('bar', __NAMESPACE__.'\Bar')->setAutowired(true);
  557. $pass = new AutowirePass();
  558. $pass->process($container);
  559. $this->assertTrue($container->hasDefinition('deprecated'));
  560. $this->assertTrue($container->hasDefinition('foo'));
  561. $this->assertTrue($container->hasDefinition('bar'));
  562. }
  563. public function testEmptyStringIsKept()
  564. {
  565. $container = new ContainerBuilder();
  566. $container->register(A::class);
  567. $container->register(Lille::class);
  568. $container->register('foo', __NAMESPACE__.'\MultipleArgumentsOptionalScalar')
  569. ->setAutowired(true)
  570. ->setArguments(array('', ''));
  571. (new ResolveClassPass())->process($container);
  572. (new AutowirePass())->process($container);
  573. $this->assertEquals(array(new TypedReference(A::class, A::class, MultipleArgumentsOptionalScalar::class), '', new TypedReference(Lille::class, Lille::class)), $container->getDefinition('foo')->getArguments());
  574. }
  575. public function testWithFactory()
  576. {
  577. $container = new ContainerBuilder();
  578. $container->register(Foo::class);
  579. $definition = $container->register('a', A::class)
  580. ->setFactory(array(A::class, 'create'))
  581. ->setAutowired(true);
  582. (new ResolveClassPass())->process($container);
  583. (new AutowirePass())->process($container);
  584. $this->assertEquals(array(new TypedReference(Foo::class, Foo::class, A::class)), $definition->getArguments());
  585. }
  586. /**
  587. * @dataProvider provideNotWireableCalls
  588. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  589. */
  590. public function testNotWireableCalls($method, $expectedMsg)
  591. {
  592. $container = new ContainerBuilder();
  593. $foo = $container->register('foo', NotWireable::class)->setAutowired(true)
  594. ->addMethodCall('setBar', array())
  595. ->addMethodCall('setOptionalNotAutowireable', array())
  596. ->addMethodCall('setOptionalNoTypeHint', array())
  597. ->addMethodCall('setOptionalArgNoAutowireable', array())
  598. ;
  599. if ($method) {
  600. $foo->addMethodCall($method, array());
  601. }
  602. if (method_exists($this, 'expectException')) {
  603. $this->expectException(RuntimeException::class);
  604. $this->expectExceptionMessage($expectedMsg);
  605. } else {
  606. $this->setExpectedException(RuntimeException::class, $expectedMsg);
  607. }
  608. (new ResolveClassPass())->process($container);
  609. (new AutowirePass())->process($container);
  610. }
  611. public function provideNotWireableCalls()
  612. {
  613. return array(
  614. array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class was not found.'),
  615. array('setDifferentNamespace', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setDifferentNamespace()" references class "stdClass" but no such service exists. It cannot be auto-registered because it is from a different root namespace.'),
  616. array(null, 'Cannot autowire service "foo": method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setProtectedMethod()" must be public.'),
  617. );
  618. }
  619. /**
  620. * @group legacy
  621. * @expectedDeprecation Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. Try changing the type-hint for argument "$i" of method "Symfony\Component\DependencyInjection\Tests\Compiler\J::__construct()" to "Symfony\Component\DependencyInjection\Tests\Compiler\IInterface" instead.
  622. * @expectedExceptionInSymfony4 \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  623. * @expectedExceptionMessageInSymfony4 Cannot autowire service "j": argument "$i" of method "Symfony\Component\DependencyInjection\Tests\Compiler\J::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\I" but no such service exists. Try changing the type-hint to "Symfony\Component\DependencyInjection\Tests\Compiler\IInterface" instead.
  624. */
  625. public function testByIdAlternative()
  626. {
  627. $container = new ContainerBuilder();
  628. $container->setAlias(IInterface::class, 'i');
  629. $container->register('i', I::class);
  630. $container->register('j', J::class)
  631. ->setAutowired(true);
  632. $pass = new AutowirePass();
  633. $pass->process($container);
  634. }
  635. /**
  636. * @group legacy
  637. * @expectedDeprecation Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. Try changing the type-hint for "Symfony\Component\DependencyInjection\Tests\Compiler\A" in "Symfony\Component\DependencyInjection\Tests\Compiler\Bar" to "Symfony\Component\DependencyInjection\Tests\Compiler\AInterface" instead.
  638. */
  639. public function testTypedReferenceDeprecationNotice()
  640. {
  641. $container = new ContainerBuilder();
  642. $container->register('aClass', A::class);
  643. $container->setAlias(AInterface::class, 'aClass');
  644. $container
  645. ->register('bar', Bar::class)
  646. ->setProperty('a', array(new TypedReference(A::class, A::class, Bar::class)))
  647. ;
  648. $pass = new AutowirePass();
  649. $pass->process($container);
  650. }
  651. /**
  652. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  653. * @expectedExceptionMessage Cannot autowire service "j": argument "$i" of method "Symfony\Component\DependencyInjection\Tests\Compiler\J::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\I" but no such service exists. Try changing the type-hint to "Symfony\Component\DependencyInjection\Tests\Compiler\IInterface" instead.
  654. */
  655. public function testExceptionWhenAliasExists()
  656. {
  657. $container = new ContainerBuilder();
  658. // multiple I services... but there *is* IInterface available
  659. $container->setAlias(IInterface::class, 'i');
  660. $container->register('i', I::class);
  661. $container->register('i2', I::class);
  662. // J type-hints against I concretely
  663. $container->register('j', J::class)
  664. ->setAutowired(true);
  665. $pass = new AutowirePass();
  666. $pass->process($container);
  667. }
  668. /**
  669. * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
  670. * @expectedExceptionMessage Cannot autowire service "j": argument "$i" of method "Symfony\Component\DependencyInjection\Tests\Compiler\J::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\I" but no such service exists. You should maybe alias this class to one of these existing services: "i", "i2".
  671. */
  672. public function testExceptionWhenAliasDoesNotExist()
  673. {
  674. $container = new ContainerBuilder();
  675. // multiple I instances... but no IInterface alias
  676. $container->register('i', I::class);
  677. $container->register('i2', I::class);
  678. // J type-hints against I concretely
  679. $container->register('j', J::class)
  680. ->setAutowired(true);
  681. $pass = new AutowirePass();
  682. $pass->process($container);
  683. }
  684. }
  685. class Foo
  686. {
  687. }
  688. class Bar
  689. {
  690. public function __construct(Foo $foo)
  691. {
  692. }
  693. }
  694. interface AInterface
  695. {
  696. }
  697. class A implements AInterface
  698. {
  699. public static function create(Foo $foo)
  700. {
  701. }
  702. }
  703. class B extends A
  704. {
  705. }
  706. class C
  707. {
  708. public function __construct(A $a)
  709. {
  710. }
  711. }
  712. interface DInterface
  713. {
  714. }
  715. interface EInterface extends DInterface
  716. {
  717. }
  718. interface IInterface
  719. {
  720. }
  721. class I implements IInterface
  722. {
  723. }
  724. class F extends I implements EInterface
  725. {
  726. }
  727. class G
  728. {
  729. public function __construct(DInterface $d, EInterface $e, IInterface $i)
  730. {
  731. }
  732. }
  733. class H
  734. {
  735. public function __construct(B $b, DInterface $d)
  736. {
  737. }
  738. }
  739. class D
  740. {
  741. public function __construct(A $a, DInterface $d)
  742. {
  743. }
  744. }
  745. class E
  746. {
  747. public function __construct(D $d = null)
  748. {
  749. }
  750. }
  751. class J
  752. {
  753. public function __construct(I $i)
  754. {
  755. }
  756. }
  757. interface CollisionInterface
  758. {
  759. }
  760. class CollisionA implements CollisionInterface
  761. {
  762. }
  763. class CollisionB implements CollisionInterface
  764. {
  765. }
  766. class CannotBeAutowired
  767. {
  768. public function __construct(CollisionInterface $collision)
  769. {
  770. }
  771. }
  772. class CannotBeAutowiredForwardOrder
  773. {
  774. public function __construct(CollisionA $a, CollisionInterface $b, CollisionB $c)
  775. {
  776. }
  777. }
  778. class CannotBeAutowiredReverseOrder
  779. {
  780. public function __construct(CollisionA $a, CollisionB $c, CollisionInterface $b)
  781. {
  782. }
  783. }
  784. class Lille
  785. {
  786. }
  787. class Dunglas
  788. {
  789. public function __construct(Lille $l)
  790. {
  791. }
  792. }
  793. class LesTilleuls
  794. {
  795. public function __construct(Dunglas $j, Dunglas $k)
  796. {
  797. }
  798. }
  799. class OptionalParameter
  800. {
  801. public function __construct(CollisionInterface $c = null, A $a, Foo $f = null)
  802. {
  803. }
  804. }
  805. class BadTypeHintedArgument
  806. {
  807. public function __construct(Dunglas $k, NotARealClass $r)
  808. {
  809. }
  810. }
  811. class BadParentTypeHintedArgument
  812. {
  813. public function __construct(Dunglas $k, OptionalServiceClass $r)
  814. {
  815. }
  816. }
  817. class NotGuessableArgument
  818. {
  819. public function __construct(Foo $k)
  820. {
  821. }
  822. }
  823. class NotGuessableArgumentForSubclass
  824. {
  825. public function __construct(A $k)
  826. {
  827. }
  828. }
  829. class MultipleArguments
  830. {
  831. public function __construct(A $k, $foo, Dunglas $dunglas)
  832. {
  833. }
  834. }
  835. class MultipleArgumentsOptionalScalar
  836. {
  837. public function __construct(A $a, $foo = 'default_val', Lille $lille = null)
  838. {
  839. }
  840. }
  841. class MultipleArgumentsOptionalScalarLast
  842. {
  843. public function __construct(A $a, Lille $lille, $foo = 'some_val')
  844. {
  845. }
  846. }
  847. class MultipleArgumentsOptionalScalarNotReallyOptional
  848. {
  849. public function __construct(A $a, $foo = 'default_val', Lille $lille)
  850. {
  851. }
  852. }
  853. /*
  854. * Classes used for testing createResourceForClass
  855. */
  856. class ClassForResource
  857. {
  858. public function __construct($foo, Bar $bar = null)
  859. {
  860. }
  861. public function setBar(Bar $bar)
  862. {
  863. }
  864. }
  865. class IdenticalClassResource extends ClassForResource
  866. {
  867. }
  868. class ClassChangedConstructorArgs extends ClassForResource
  869. {
  870. public function __construct($foo, Bar $bar, $baz)
  871. {
  872. }
  873. }
  874. class SetterInjection extends SetterInjectionParent
  875. {
  876. /**
  877. * @required
  878. */
  879. public function setFoo(Foo $foo)
  880. {
  881. // should be called
  882. }
  883. /** @inheritdoc*/
  884. public function setDependencies(Foo $foo, A $a)
  885. {
  886. // should be called
  887. }
  888. /** {@inheritdoc} */
  889. public function setWithCallsConfigured(A $a)
  890. {
  891. // this method has a calls configured on it
  892. }
  893. public function notASetter(A $a)
  894. {
  895. // should be called only when explicitly specified
  896. }
  897. /**
  898. * @required*/
  899. public function setChildMethodWithoutDocBlock(A $a)
  900. {
  901. }
  902. }
  903. class SetterInjectionParent
  904. {
  905. /** @required*/
  906. public function setDependencies(Foo $foo, A $a)
  907. {
  908. // should be called
  909. }
  910. public function notASetter(A $a)
  911. {
  912. // @required should be ignored when the child does not add @inheritdoc
  913. }
  914. /** @required <tab> prefix is on purpose */
  915. public function setWithCallsConfigured(A $a)
  916. {
  917. }
  918. /** @required */
  919. public function setChildMethodWithoutDocBlock(A $a)
  920. {
  921. }
  922. }
  923. class SetterInjectionCollision
  924. {
  925. /**
  926. * @required
  927. */
  928. public function setMultipleInstancesForOneArg(CollisionInterface $collision)
  929. {
  930. // The CollisionInterface cannot be autowired - there are multiple
  931. // should throw an exception
  932. }
  933. }
  934. class NotWireable
  935. {
  936. public function setNotAutowireable(NotARealClass $n)
  937. {
  938. }
  939. public function setBar()
  940. {
  941. }
  942. public function setOptionalNotAutowireable(NotARealClass $n = null)
  943. {
  944. }
  945. public function setDifferentNamespace(\stdClass $n)
  946. {
  947. }
  948. public function setOptionalNoTypeHint($foo = null)
  949. {
  950. }
  951. public function setOptionalArgNoAutowireable($other = 'default_val')
  952. {
  953. }
  954. /** @required */
  955. protected function setProtectedMethod(A $a)
  956. {
  957. }
  958. }
  959. class PrivateConstructor
  960. {
  961. private function __construct()
  962. {
  963. }
  964. }