PhpDumper.php 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317
  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\Dumper;
  11. use Symfony\Component\DependencyInjection\Variable;
  12. use Symfony\Component\DependencyInjection\Definition;
  13. use Symfony\Component\DependencyInjection\ContainerBuilder;
  14. use Symfony\Component\DependencyInjection\Container;
  15. use Symfony\Component\DependencyInjection\ContainerInterface;
  16. use Symfony\Component\DependencyInjection\Reference;
  17. use Symfony\Component\DependencyInjection\Parameter;
  18. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  19. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  20. use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
  21. use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper;
  22. use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper;
  23. /**
  24. * PhpDumper dumps a service container as a PHP class.
  25. *
  26. * @author Fabien Potencier <fabien@symfony.com>
  27. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  28. *
  29. * @api
  30. */
  31. class PhpDumper extends Dumper
  32. {
  33. /**
  34. * Characters that might appear in the generated variable name as first character
  35. * @var string
  36. */
  37. const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz';
  38. /**
  39. * Characters that might appear in the generated variable name as any but the first character
  40. * @var string
  41. */
  42. const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_';
  43. private $inlinedDefinitions;
  44. private $definitionVariables;
  45. private $referenceVariables;
  46. private $variableCount;
  47. private $reservedVariables = array('instance', 'class');
  48. /**
  49. * @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface
  50. */
  51. private $proxyDumper;
  52. /**
  53. * {@inheritDoc}
  54. *
  55. * @api
  56. */
  57. public function __construct(ContainerBuilder $container)
  58. {
  59. parent::__construct($container);
  60. $this->inlinedDefinitions = new \SplObjectStorage;
  61. }
  62. /**
  63. * Sets the dumper to be used when dumping proxies in the generated container.
  64. *
  65. * @param ProxyDumper $proxyDumper
  66. */
  67. public function setProxyDumper(ProxyDumper $proxyDumper)
  68. {
  69. $this->proxyDumper = $proxyDumper;
  70. }
  71. /**
  72. * Dumps the service container as a PHP class.
  73. *
  74. * Available options:
  75. *
  76. * * class: The class name
  77. * * base_class: The base class name
  78. *
  79. * @param array $options An array of options
  80. *
  81. * @return string A PHP class representing of the service container
  82. *
  83. * @api
  84. */
  85. public function dump(array $options = array())
  86. {
  87. $options = array_merge(array(
  88. 'class' => 'ProjectServiceContainer',
  89. 'base_class' => 'Container',
  90. ), $options);
  91. $code = $this->startClass($options['class'], $options['base_class']);
  92. if ($this->container->isFrozen()) {
  93. $code .= $this->addFrozenConstructor();
  94. } else {
  95. $code .= $this->addConstructor();
  96. }
  97. $code .=
  98. $this->addServices().
  99. $this->addDefaultParametersMethod().
  100. $this->endClass().
  101. $this->addProxyClasses()
  102. ;
  103. return $code;
  104. }
  105. /**
  106. * Retrieves the currently set proxy dumper or instantiates one.
  107. *
  108. * @return ProxyDumper
  109. */
  110. private function getProxyDumper()
  111. {
  112. if (!$this->proxyDumper) {
  113. $this->proxyDumper = new NullDumper();
  114. }
  115. return $this->proxyDumper;
  116. }
  117. /**
  118. * Generates Service local temp variables.
  119. *
  120. * @param string $cId
  121. * @param string $definition
  122. *
  123. * @return string
  124. */
  125. private function addServiceLocalTempVariables($cId, $definition)
  126. {
  127. static $template = " \$%s = %s;\n";
  128. $localDefinitions = array_merge(
  129. array($definition),
  130. $this->getInlinedDefinitions($definition)
  131. );
  132. $calls = $behavior = array();
  133. foreach ($localDefinitions as $iDefinition) {
  134. $this->getServiceCallsFromArguments($iDefinition->getArguments(), $calls, $behavior);
  135. $this->getServiceCallsFromArguments($iDefinition->getMethodCalls(), $calls, $behavior);
  136. $this->getServiceCallsFromArguments($iDefinition->getProperties(), $calls, $behavior);
  137. }
  138. $code = '';
  139. foreach ($calls as $id => $callCount) {
  140. if ('service_container' === $id || $id === $cId) {
  141. continue;
  142. }
  143. if ($callCount > 1) {
  144. $name = $this->getNextVariableName();
  145. $this->referenceVariables[$id] = new Variable($name);
  146. if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id]) {
  147. $code .= sprintf($template, $name, $this->getServiceCall($id));
  148. } else {
  149. $code .= sprintf($template, $name, $this->getServiceCall($id, new Reference($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)));
  150. }
  151. }
  152. }
  153. if ('' !== $code) {
  154. $code .= "\n";
  155. }
  156. return $code;
  157. }
  158. /**
  159. * Generates code for the proxies to be attached after the container class
  160. *
  161. * @return string
  162. */
  163. private function addProxyClasses()
  164. {
  165. /* @var $proxyDefinitions Definition[] */
  166. $definitions = array_filter(
  167. $this->container->getDefinitions(),
  168. array($this->getProxyDumper(), 'isProxyCandidate')
  169. );
  170. $code = '';
  171. foreach ($definitions as $definition) {
  172. $code .= "\n" . $this->getProxyDumper()->getProxyCode($definition);
  173. }
  174. return $code;
  175. }
  176. /**
  177. * Generates the require_once statement for service includes.
  178. *
  179. * @param string $id The service id
  180. * @param Definition $definition
  181. *
  182. * @return string
  183. */
  184. private function addServiceInclude($id, $definition)
  185. {
  186. $template = " require_once %s;\n";
  187. $code = '';
  188. if (null !== $file = $definition->getFile()) {
  189. $code .= sprintf($template, $this->dumpValue($file));
  190. }
  191. foreach ($this->getInlinedDefinitions($definition) as $definition) {
  192. if (null !== $file = $definition->getFile()) {
  193. $code .= sprintf($template, $this->dumpValue($file));
  194. }
  195. }
  196. if ('' !== $code) {
  197. $code .= "\n";
  198. }
  199. return $code;
  200. }
  201. /**
  202. * Generates the inline definition of a service.
  203. *
  204. * @param string $id
  205. * @param Definition $definition
  206. *
  207. * @return string
  208. *
  209. * @throws RuntimeException When the factory definition is incomplete
  210. * @throws ServiceCircularReferenceException When a circular reference is detected
  211. */
  212. private function addServiceInlinedDefinitions($id, $definition)
  213. {
  214. $code = '';
  215. $variableMap = $this->definitionVariables;
  216. $nbOccurrences = new \SplObjectStorage();
  217. $processed = new \SplObjectStorage();
  218. $inlinedDefinitions = $this->getInlinedDefinitions($definition);
  219. foreach ($inlinedDefinitions as $definition) {
  220. if (false === $nbOccurrences->contains($definition)) {
  221. $nbOccurrences->offsetSet($definition, 1);
  222. } else {
  223. $i = $nbOccurrences->offsetGet($definition);
  224. $nbOccurrences->offsetSet($definition, $i + 1);
  225. }
  226. }
  227. foreach ($inlinedDefinitions as $sDefinition) {
  228. if ($processed->contains($sDefinition)) {
  229. continue;
  230. }
  231. $processed->offsetSet($sDefinition);
  232. $class = $this->dumpValue($sDefinition->getClass());
  233. if ($nbOccurrences->offsetGet($sDefinition) > 1 || $sDefinition->getMethodCalls() || $sDefinition->getProperties() || null !== $sDefinition->getConfigurator() || false !== strpos($class, '$')) {
  234. $name = $this->getNextVariableName();
  235. $variableMap->offsetSet($sDefinition, new Variable($name));
  236. // a construct like:
  237. // $a = new ServiceA(ServiceB $b); $b = new ServiceB(ServiceA $a);
  238. // this is an indication for a wrong implementation, you can circumvent this problem
  239. // by setting up your service structure like this:
  240. // $b = new ServiceB();
  241. // $a = new ServiceA(ServiceB $b);
  242. // $b->setServiceA(ServiceA $a);
  243. if ($this->hasReference($id, $sDefinition->getArguments())) {
  244. throw new ServiceCircularReferenceException($id, array($id));
  245. }
  246. $code .= $this->addNewInstance($id, $sDefinition, '$'.$name, ' = ');
  247. if (!$this->hasReference($id, $sDefinition->getMethodCalls(), true) && !$this->hasReference($id, $sDefinition->getProperties(), true)) {
  248. $code .= $this->addServiceMethodCalls(null, $sDefinition, $name);
  249. $code .= $this->addServiceProperties(null, $sDefinition, $name);
  250. $code .= $this->addServiceConfigurator(null, $sDefinition, $name);
  251. }
  252. $code .= "\n";
  253. }
  254. }
  255. return $code;
  256. }
  257. /**
  258. * Adds the service return statement.
  259. *
  260. * @param string $id Service id
  261. * @param Definition $definition
  262. *
  263. * @return string
  264. */
  265. private function addServiceReturn($id, $definition)
  266. {
  267. if ($this->isSimpleInstance($id, $definition)) {
  268. return " }\n";
  269. }
  270. return "\n return \$instance;\n }\n";
  271. }
  272. /**
  273. * Generates the service instance.
  274. *
  275. * @param string $id
  276. * @param Definition $definition
  277. *
  278. * @return string
  279. *
  280. * @throws InvalidArgumentException
  281. * @throws RuntimeException
  282. */
  283. private function addServiceInstance($id, $definition)
  284. {
  285. $class = $this->dumpValue($definition->getClass());
  286. if (0 === strpos($class, "'") && !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
  287. throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id));
  288. }
  289. $simple = $this->isSimpleInstance($id, $definition);
  290. $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
  291. $instantiation = '';
  292. if (!$isProxyCandidate && ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) {
  293. $instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance');
  294. } elseif (!$isProxyCandidate && ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) {
  295. $instantiation = "\$this->services['$id'] = \$this->scopedServices['$scope']['$id'] = ".($simple ? '' : '$instance');
  296. } elseif (!$simple) {
  297. $instantiation = '$instance';
  298. }
  299. $return = '';
  300. if ($simple) {
  301. $return = 'return ';
  302. } else {
  303. $instantiation .= ' = ';
  304. }
  305. $code = $this->addNewInstance($id, $definition, $return, $instantiation);
  306. if (!$simple) {
  307. $code .= "\n";
  308. }
  309. return $code;
  310. }
  311. /**
  312. * Checks if the definition is a simple instance.
  313. *
  314. * @param string $id
  315. * @param Definition $definition
  316. *
  317. * @return Boolean
  318. */
  319. private function isSimpleInstance($id, $definition)
  320. {
  321. foreach (array_merge(array($definition), $this->getInlinedDefinitions($definition)) as $sDefinition) {
  322. if ($definition !== $sDefinition && !$this->hasReference($id, $sDefinition->getMethodCalls())) {
  323. continue;
  324. }
  325. if ($sDefinition->getMethodCalls() || $sDefinition->getProperties() || $sDefinition->getConfigurator()) {
  326. return false;
  327. }
  328. }
  329. return true;
  330. }
  331. /**
  332. * Adds method calls to a service definition.
  333. *
  334. * @param string $id
  335. * @param Definition $definition
  336. * @param string $variableName
  337. *
  338. * @return string
  339. */
  340. private function addServiceMethodCalls($id, $definition, $variableName = 'instance')
  341. {
  342. $calls = '';
  343. foreach ($definition->getMethodCalls() as $call) {
  344. $arguments = array();
  345. foreach ($call[1] as $value) {
  346. $arguments[] = $this->dumpValue($value);
  347. }
  348. $calls .= $this->wrapServiceConditionals($call[1], sprintf(" \$%s->%s(%s);\n", $variableName, $call[0], implode(', ', $arguments)));
  349. }
  350. return $calls;
  351. }
  352. private function addServiceProperties($id, $definition, $variableName = 'instance')
  353. {
  354. $code = '';
  355. foreach ($definition->getProperties() as $name => $value) {
  356. $code .= sprintf(" \$%s->%s = %s;\n", $variableName, $name, $this->dumpValue($value));
  357. }
  358. return $code;
  359. }
  360. /**
  361. * Generates the inline definition setup.
  362. *
  363. * @param string $id
  364. * @param Definition $definition
  365. * @return string
  366. */
  367. private function addServiceInlinedDefinitionsSetup($id, $definition)
  368. {
  369. $this->referenceVariables[$id] = new Variable('instance');
  370. $code = '';
  371. $processed = new \SplObjectStorage();
  372. foreach ($this->getInlinedDefinitions($definition) as $iDefinition) {
  373. if ($processed->contains($iDefinition)) {
  374. continue;
  375. }
  376. $processed->offsetSet($iDefinition);
  377. if (!$this->hasReference($id, $iDefinition->getMethodCalls(), true) && !$this->hasReference($id, $iDefinition->getProperties(), true)) {
  378. continue;
  379. }
  380. $name = (string) $this->definitionVariables->offsetGet($iDefinition);
  381. $code .= $this->addServiceMethodCalls(null, $iDefinition, $name);
  382. $code .= $this->addServiceProperties(null, $iDefinition, $name);
  383. $code .= $this->addServiceConfigurator(null, $iDefinition, $name);
  384. }
  385. if ('' !== $code) {
  386. $code .= "\n";
  387. }
  388. return $code;
  389. }
  390. /**
  391. * Adds configurator definition
  392. *
  393. * @param string $id
  394. * @param Definition $definition
  395. * @param string $variableName
  396. *
  397. * @return string
  398. */
  399. private function addServiceConfigurator($id, $definition, $variableName = 'instance')
  400. {
  401. if (!$callable = $definition->getConfigurator()) {
  402. return '';
  403. }
  404. if (is_array($callable)) {
  405. if ($callable[0] instanceof Reference) {
  406. return sprintf(" %s->%s(\$%s);\n", $this->getServiceCall((string) $callable[0]), $callable[1], $variableName);
  407. }
  408. return sprintf(" call_user_func(array(%s, '%s'), \$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
  409. }
  410. return sprintf(" %s(\$%s);\n", $callable, $variableName);
  411. }
  412. /**
  413. * Adds a service
  414. *
  415. * @param string $id
  416. * @param Definition $definition
  417. *
  418. * @return string
  419. */
  420. private function addService($id, $definition)
  421. {
  422. $this->definitionVariables = new \SplObjectStorage();
  423. $this->referenceVariables = array();
  424. $this->variableCount = 0;
  425. $return = array();
  426. if ($definition->isSynthetic()) {
  427. $return[] = '@throws RuntimeException always since this service is expected to be injected dynamically';
  428. } elseif ($class = $definition->getClass()) {
  429. $return[] = sprintf("@return %s A %s instance.", 0 === strpos($class, '%') ? 'object' : $class, $class);
  430. } elseif ($definition->getFactoryClass()) {
  431. $return[] = sprintf('@return object An instance returned by %s::%s().', $definition->getFactoryClass(), $definition->getFactoryMethod());
  432. } elseif ($definition->getFactoryService()) {
  433. $return[] = sprintf('@return object An instance returned by %s::%s().', $definition->getFactoryService(), $definition->getFactoryMethod());
  434. }
  435. $scope = $definition->getScope();
  436. if (!in_array($scope, array(ContainerInterface::SCOPE_CONTAINER, ContainerInterface::SCOPE_PROTOTYPE))) {
  437. if ($return && 0 === strpos($return[count($return) - 1], '@return')) {
  438. $return[] = '';
  439. }
  440. $return[] = sprintf("@throws InactiveScopeException when the '%s' service is requested while the '%s' scope is not active", $id, $scope);
  441. }
  442. $return = implode("\n * ", $return);
  443. $doc = '';
  444. if (ContainerInterface::SCOPE_PROTOTYPE !== $scope) {
  445. $doc .= <<<EOF
  446. *
  447. * This service is shared.
  448. * This method always returns the same instance of the service.
  449. EOF;
  450. }
  451. if (!$definition->isPublic()) {
  452. $doc .= <<<EOF
  453. *
  454. * This service is private.
  455. * If you want to be able to request this service from the container directly,
  456. * make it public, otherwise you might end up with broken code.
  457. EOF;
  458. }
  459. if ($definition->isLazy()) {
  460. $lazyInitialization = '$lazyLoad = true';
  461. $lazyInitializationDoc = "\n * @param boolean \$lazyLoad whether to try lazy-loading the service with a proxy\n *";
  462. } else {
  463. $lazyInitialization = '';
  464. $lazyInitializationDoc = '';
  465. }
  466. // with proxies, for 5.3.3 compatibility, the getter must be public to be accessible to the initializer
  467. $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
  468. $visibility = $isProxyCandidate ? 'public' : 'protected';
  469. $code = <<<EOF
  470. /**
  471. * Gets the '$id' service.$doc
  472. *$lazyInitializationDoc
  473. * $return
  474. */
  475. {$visibility} function get{$this->camelize($id)}Service($lazyInitialization)
  476. {
  477. EOF;
  478. $code .= $isProxyCandidate ? $this->getProxyDumper()->getProxyFactoryCode($definition, $id) : '';
  479. if (!in_array($scope, array(ContainerInterface::SCOPE_CONTAINER, ContainerInterface::SCOPE_PROTOTYPE))) {
  480. $code .= <<<EOF
  481. if (!isset(\$this->scopedServices['$scope'])) {
  482. throw new InactiveScopeException('$id', '$scope');
  483. }
  484. EOF;
  485. }
  486. if ($definition->isSynthetic()) {
  487. $code .= sprintf(" throw new RuntimeException('You have requested a synthetic service (\"%s\"). The DIC does not know how to construct this service.');\n }\n", $id);
  488. } else {
  489. $code .=
  490. $this->addServiceInclude($id, $definition).
  491. $this->addServiceLocalTempVariables($id, $definition).
  492. $this->addServiceInlinedDefinitions($id, $definition).
  493. $this->addServiceInstance($id, $definition).
  494. $this->addServiceInlinedDefinitionsSetup($id, $definition).
  495. $this->addServiceMethodCalls($id, $definition).
  496. $this->addServiceProperties($id, $definition).
  497. $this->addServiceConfigurator($id, $definition).
  498. $this->addServiceReturn($id, $definition)
  499. ;
  500. }
  501. $this->definitionVariables = null;
  502. $this->referenceVariables = null;
  503. return $code;
  504. }
  505. /**
  506. * Adds multiple services
  507. *
  508. * @return string
  509. */
  510. private function addServices()
  511. {
  512. $publicServices = $privateServices = $synchronizers = '';
  513. $definitions = $this->container->getDefinitions();
  514. ksort($definitions);
  515. foreach ($definitions as $id => $definition) {
  516. if ($definition->isPublic()) {
  517. $publicServices .= $this->addService($id, $definition);
  518. } else {
  519. $privateServices .= $this->addService($id, $definition);
  520. }
  521. $synchronizers .= $this->addServiceSynchronizer($id, $definition);
  522. }
  523. return $publicServices.$synchronizers.$privateServices;
  524. }
  525. /**
  526. * Adds synchronizer methods.
  527. *
  528. * @param string $id A service identifier
  529. * @param Definition $definition A Definition instance
  530. */
  531. private function addServiceSynchronizer($id, Definition $definition)
  532. {
  533. if (!$definition->isSynchronized()) {
  534. return;
  535. }
  536. $code = '';
  537. foreach ($this->container->getDefinitions() as $definitionId => $definition) {
  538. foreach ($definition->getMethodCalls() as $call) {
  539. foreach ($call[1] as $argument) {
  540. if ($argument instanceof Reference && $id == (string) $argument) {
  541. $arguments = array();
  542. foreach ($call[1] as $value) {
  543. $arguments[] = $this->dumpValue($value);
  544. }
  545. $call = $this->wrapServiceConditionals($call[1], sprintf("\$this->get('%s')->%s(%s);", $definitionId, $call[0], implode(', ', $arguments)));
  546. $code .= <<<EOF
  547. if (\$this->initialized('$definitionId')) {
  548. $call
  549. }
  550. EOF;
  551. }
  552. }
  553. }
  554. }
  555. if (!$code) {
  556. return;
  557. }
  558. return <<<EOF
  559. /**
  560. * Updates the '$id' service.
  561. */
  562. protected function synchronize{$this->camelize($id)}Service()
  563. {
  564. $code }
  565. EOF;
  566. }
  567. private function addNewInstance($id, Definition $definition, $return, $instantiation)
  568. {
  569. $class = $this->dumpValue($definition->getClass());
  570. $arguments = array();
  571. foreach ($definition->getArguments() as $value) {
  572. $arguments[] = $this->dumpValue($value);
  573. }
  574. if (null !== $definition->getFactoryMethod()) {
  575. if (null !== $definition->getFactoryClass()) {
  576. return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($definition->getFactoryClass()), $definition->getFactoryMethod(), $arguments ? ', '.implode(', ', $arguments) : '');
  577. }
  578. if (null !== $definition->getFactoryService()) {
  579. return sprintf(" $return{$instantiation}%s->%s(%s);\n", $this->getServiceCall($definition->getFactoryService()), $definition->getFactoryMethod(), implode(', ', $arguments));
  580. }
  581. throw new RuntimeException(sprintf('Factory method requires a factory service or factory class in service definition for %s', $id));
  582. }
  583. if (false !== strpos($class, '$')) {
  584. return sprintf(" \$class = %s;\n\n $return{$instantiation}new \$class(%s);\n", $class, implode(', ', $arguments));
  585. }
  586. return sprintf(" $return{$instantiation}new \\%s(%s);\n", substr(str_replace('\\\\', '\\', $class), 1, -1), implode(', ', $arguments));
  587. }
  588. /**
  589. * Adds the class headers.
  590. *
  591. * @param string $class Class name
  592. * @param string $baseClass The name of the base class
  593. *
  594. * @return string
  595. */
  596. private function startClass($class, $baseClass)
  597. {
  598. $bagClass = $this->container->isFrozen() ? 'use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;' : 'use Symfony\Component\DependencyInjection\ParameterBag\\ParameterBag;';
  599. return <<<EOF
  600. <?php
  601. use Symfony\Component\DependencyInjection\ContainerInterface;
  602. use Symfony\Component\DependencyInjection\Container;
  603. use Symfony\Component\DependencyInjection\Exception\InactiveScopeException;
  604. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  605. use Symfony\Component\DependencyInjection\Exception\LogicException;
  606. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  607. use Symfony\Component\DependencyInjection\Reference;
  608. use Symfony\Component\DependencyInjection\Parameter;
  609. $bagClass
  610. /**
  611. * $class
  612. *
  613. * This class has been auto-generated
  614. * by the Symfony Dependency Injection Component.
  615. */
  616. class $class extends $baseClass
  617. {
  618. EOF;
  619. }
  620. /**
  621. * Adds the constructor.
  622. *
  623. * @return string
  624. */
  625. private function addConstructor()
  626. {
  627. $arguments = $this->container->getParameterBag()->all() ? 'new ParameterBag($this->getDefaultParameters())' : null;
  628. $code = <<<EOF
  629. /**
  630. * Constructor.
  631. */
  632. public function __construct()
  633. {
  634. parent::__construct($arguments);
  635. EOF;
  636. if (count($scopes = $this->container->getScopes()) > 0) {
  637. $code .= "\n";
  638. $code .= " \$this->scopes = ".$this->dumpValue($scopes).";\n";
  639. $code .= " \$this->scopeChildren = ".$this->dumpValue($this->container->getScopeChildren()).";\n";
  640. }
  641. $code .= $this->addMethodMap();
  642. $code .= $this->addAliases();
  643. $code .= <<<EOF
  644. }
  645. EOF;
  646. return $code;
  647. }
  648. /**
  649. * Adds the constructor for a frozen container.
  650. *
  651. * @return string
  652. */
  653. private function addFrozenConstructor()
  654. {
  655. $code = <<<EOF
  656. /**
  657. * Constructor.
  658. */
  659. public function __construct()
  660. {
  661. EOF;
  662. if ($this->container->getParameterBag()->all()) {
  663. $code .= "\n \$this->parameters = \$this->getDefaultParameters();\n";
  664. }
  665. $code .= <<<EOF
  666. \$this->services =
  667. \$this->scopedServices =
  668. \$this->scopeStacks = array();
  669. \$this->set('service_container', \$this);
  670. EOF;
  671. $code .= "\n";
  672. if (count($scopes = $this->container->getScopes()) > 0) {
  673. $code .= " \$this->scopes = ".$this->dumpValue($scopes).";\n";
  674. $code .= " \$this->scopeChildren = ".$this->dumpValue($this->container->getScopeChildren()).";\n";
  675. } else {
  676. $code .= " \$this->scopes = array();\n";
  677. $code .= " \$this->scopeChildren = array();\n";
  678. }
  679. $code .= $this->addMethodMap();
  680. $code .= $this->addAliases();
  681. $code .= <<<EOF
  682. }
  683. EOF;
  684. return $code;
  685. }
  686. /**
  687. * Adds the methodMap property definition
  688. *
  689. * @return string
  690. */
  691. private function addMethodMap()
  692. {
  693. if (!$definitions = $this->container->getDefinitions()) {
  694. return '';
  695. }
  696. $code = " \$this->methodMap = array(\n";
  697. ksort($definitions);
  698. foreach ($definitions as $id => $definition) {
  699. $code .= ' '.var_export($id, true).' => '.var_export('get'.$this->camelize($id).'Service', true).",\n";
  700. }
  701. return $code . " );\n";
  702. }
  703. /**
  704. * Adds the aliases property definition
  705. *
  706. * @return string
  707. */
  708. private function addAliases()
  709. {
  710. if (!$aliases = $this->container->getAliases()) {
  711. if ($this->container->isFrozen()) {
  712. return "\n \$this->aliases = array();\n";
  713. } else {
  714. return '';
  715. }
  716. }
  717. $code = " \$this->aliases = array(\n";
  718. ksort($aliases);
  719. foreach ($aliases as $alias => $id) {
  720. $id = (string) $id;
  721. while (isset($aliases[$id])) {
  722. $id = (string) $aliases[$id];
  723. }
  724. $code .= ' '.var_export($alias, true).' => '.var_export($id, true).",\n";
  725. }
  726. return $code . " );\n";
  727. }
  728. /**
  729. * Adds default parameters method.
  730. *
  731. * @return string
  732. */
  733. private function addDefaultParametersMethod()
  734. {
  735. if (!$this->container->getParameterBag()->all()) {
  736. return '';
  737. }
  738. $parameters = $this->exportParameters($this->container->getParameterBag()->all());
  739. $code = '';
  740. if ($this->container->isFrozen()) {
  741. $code .= <<<EOF
  742. /**
  743. * {@inheritdoc}
  744. */
  745. public function getParameter(\$name)
  746. {
  747. \$name = strtolower(\$name);
  748. if (!(isset(\$this->parameters[\$name]) || array_key_exists(\$name, \$this->parameters))) {
  749. throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', \$name));
  750. }
  751. return \$this->parameters[\$name];
  752. }
  753. /**
  754. * {@inheritdoc}
  755. */
  756. public function hasParameter(\$name)
  757. {
  758. \$name = strtolower(\$name);
  759. return isset(\$this->parameters[\$name]) || array_key_exists(\$name, \$this->parameters);
  760. }
  761. /**
  762. * {@inheritdoc}
  763. */
  764. public function setParameter(\$name, \$value)
  765. {
  766. throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
  767. }
  768. /**
  769. * {@inheritDoc}
  770. */
  771. public function getParameterBag()
  772. {
  773. if (null === \$this->parameterBag) {
  774. \$this->parameterBag = new FrozenParameterBag(\$this->parameters);
  775. }
  776. return \$this->parameterBag;
  777. }
  778. EOF;
  779. }
  780. $code .= <<<EOF
  781. /**
  782. * Gets the default parameters.
  783. *
  784. * @return array An array of the default parameters
  785. */
  786. protected function getDefaultParameters()
  787. {
  788. return $parameters;
  789. }
  790. EOF;
  791. return $code;
  792. }
  793. /**
  794. * Exports parameters.
  795. *
  796. * @param array $parameters
  797. * @param string $path
  798. * @param integer $indent
  799. *
  800. * @return string
  801. *
  802. * @throws InvalidArgumentException
  803. */
  804. private function exportParameters($parameters, $path = '', $indent = 12)
  805. {
  806. $php = array();
  807. foreach ($parameters as $key => $value) {
  808. if (is_array($value)) {
  809. $value = $this->exportParameters($value, $path.'/'.$key, $indent + 4);
  810. } elseif ($value instanceof Variable) {
  811. throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path.'/'.$key));
  812. } elseif ($value instanceof Definition) {
  813. throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain service definitions. Definition for "%s" found in "%s".', $value->getClass(), $path.'/'.$key));
  814. } elseif ($value instanceof Reference) {
  815. throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain references to other services (reference to service "%s" found in "%s").', $value, $path.'/'.$key));
  816. } else {
  817. $value = var_export($value, true);
  818. }
  819. $php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), var_export($key, true), $value);
  820. }
  821. return sprintf("array(\n%s\n%s)", implode("\n", $php), str_repeat(' ', $indent - 4));
  822. }
  823. /**
  824. * Ends the class definition.
  825. *
  826. * @return string
  827. */
  828. private function endClass()
  829. {
  830. return <<<EOF
  831. }
  832. EOF;
  833. }
  834. /**
  835. * Wraps the service conditionals.
  836. *
  837. * @param string $value
  838. * @param string $code
  839. *
  840. * @return string
  841. */
  842. private function wrapServiceConditionals($value, $code)
  843. {
  844. if (!$services = ContainerBuilder::getServiceConditionals($value)) {
  845. return $code;
  846. }
  847. $conditions = array();
  848. foreach ($services as $service) {
  849. $conditions[] = sprintf("\$this->has('%s')", $service);
  850. }
  851. // re-indent the wrapped code
  852. $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code)));
  853. return sprintf(" if (%s) {\n%s }\n", implode(' && ', $conditions), $code);
  854. }
  855. /**
  856. * Builds service calls from arguments.
  857. *
  858. * @param array $arguments
  859. * @param array &$calls By reference
  860. * @param array &$behavior By reference
  861. */
  862. private function getServiceCallsFromArguments(array $arguments, array &$calls, array &$behavior)
  863. {
  864. foreach ($arguments as $argument) {
  865. if (is_array($argument)) {
  866. $this->getServiceCallsFromArguments($argument, $calls, $behavior);
  867. } elseif ($argument instanceof Reference) {
  868. $id = (string) $argument;
  869. if (!isset($calls[$id])) {
  870. $calls[$id] = 0;
  871. }
  872. if (!isset($behavior[$id])) {
  873. $behavior[$id] = $argument->getInvalidBehavior();
  874. } elseif (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $behavior[$id]) {
  875. $behavior[$id] = $argument->getInvalidBehavior();
  876. }
  877. $calls[$id] += 1;
  878. }
  879. }
  880. }
  881. /**
  882. * Returns the inline definition.
  883. *
  884. * @param Definition $definition
  885. *
  886. * @return array
  887. */
  888. private function getInlinedDefinitions(Definition $definition)
  889. {
  890. if (false === $this->inlinedDefinitions->contains($definition)) {
  891. $definitions = array_merge(
  892. $this->getDefinitionsFromArguments($definition->getArguments()),
  893. $this->getDefinitionsFromArguments($definition->getMethodCalls()),
  894. $this->getDefinitionsFromArguments($definition->getProperties())
  895. );
  896. $this->inlinedDefinitions->offsetSet($definition, $definitions);
  897. return $definitions;
  898. }
  899. return $this->inlinedDefinitions->offsetGet($definition);
  900. }
  901. /**
  902. * Gets the definition from arguments.
  903. *
  904. * @param array $arguments
  905. *
  906. * @return array
  907. */
  908. private function getDefinitionsFromArguments(array $arguments)
  909. {
  910. $definitions = array();
  911. foreach ($arguments as $argument) {
  912. if (is_array($argument)) {
  913. $definitions = array_merge($definitions, $this->getDefinitionsFromArguments($argument));
  914. } elseif ($argument instanceof Definition) {
  915. $definitions = array_merge(
  916. $definitions,
  917. $this->getInlinedDefinitions($argument),
  918. array($argument)
  919. );
  920. }
  921. }
  922. return $definitions;
  923. }
  924. /**
  925. * Checks if a service id has a reference.
  926. *
  927. * @param string $id
  928. * @param array $arguments
  929. * @param Boolean $deep
  930. * @param array $visited
  931. *
  932. * @return Boolean
  933. */
  934. private function hasReference($id, array $arguments, $deep = false, $visited = array())
  935. {
  936. foreach ($arguments as $argument) {
  937. if (is_array($argument)) {
  938. if ($this->hasReference($id, $argument, $deep, $visited)) {
  939. return true;
  940. }
  941. } elseif ($argument instanceof Reference) {
  942. if ($id === (string) $argument) {
  943. return true;
  944. }
  945. if ($deep && !isset($visited[(string) $argument])) {
  946. $visited[(string) $argument] = true;
  947. $service = $this->container->getDefinition((string) $argument);
  948. $arguments = array_merge($service->getMethodCalls(), $service->getArguments(), $service->getProperties());
  949. if ($this->hasReference($id, $arguments, $deep, $visited)) {
  950. return true;
  951. }
  952. }
  953. }
  954. }
  955. return false;
  956. }
  957. /**
  958. * Dumps values.
  959. *
  960. * @param array $value
  961. * @param Boolean $interpolate
  962. *
  963. * @return string
  964. *
  965. * @throws RuntimeException
  966. */
  967. private function dumpValue($value, $interpolate = true)
  968. {
  969. if (is_array($value)) {
  970. $code = array();
  971. foreach ($value as $k => $v) {
  972. $code[] = sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate));
  973. }
  974. return sprintf('array(%s)', implode(', ', $code));
  975. } elseif ($value instanceof Definition) {
  976. if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) {
  977. return $this->dumpValue($this->definitionVariables->offsetGet($value), $interpolate);
  978. }
  979. if (count($value->getMethodCalls()) > 0) {
  980. throw new RuntimeException('Cannot dump definitions which have method calls.');
  981. }
  982. if (null !== $value->getConfigurator()) {
  983. throw new RuntimeException('Cannot dump definitions which have a configurator.');
  984. }
  985. $arguments = array();
  986. foreach ($value->getArguments() as $argument) {
  987. $arguments[] = $this->dumpValue($argument);
  988. }
  989. $class = $this->dumpValue($value->getClass());
  990. if (false !== strpos($class, '$')) {
  991. throw new RuntimeException('Cannot dump definitions which have a variable class name.');
  992. }
  993. if (null !== $value->getFactoryMethod()) {
  994. if (null !== $value->getFactoryClass()) {
  995. return sprintf("call_user_func(array(%s, '%s')%s)", $this->dumpValue($value->getFactoryClass()), $value->getFactoryMethod(), count($arguments) > 0 ? ', '.implode(', ', $arguments) : '');
  996. } elseif (null !== $value->getFactoryService()) {
  997. return sprintf("%s->%s(%s)", $this->getServiceCall($value->getFactoryService()), $value->getFactoryMethod(), implode(', ', $arguments));
  998. } else {
  999. throw new RuntimeException('Cannot dump definitions which have factory method without factory service or factory class.');
  1000. }
  1001. }
  1002. return sprintf("new \\%s(%s)", substr(str_replace('\\\\', '\\', $class), 1, -1), implode(', ', $arguments));
  1003. } elseif ($value instanceof Variable) {
  1004. return '$'.$value;
  1005. } elseif ($value instanceof Reference) {
  1006. if (null !== $this->referenceVariables && isset($this->referenceVariables[$id = (string) $value])) {
  1007. return $this->dumpValue($this->referenceVariables[$id], $interpolate);
  1008. }
  1009. return $this->getServiceCall((string) $value, $value);
  1010. } elseif ($value instanceof Parameter) {
  1011. return $this->dumpParameter($value);
  1012. } elseif (true === $interpolate && is_string($value)) {
  1013. if (preg_match('/^%([^%]+)%$/', $value, $match)) {
  1014. // we do this to deal with non string values (Boolean, integer, ...)
  1015. // the preg_replace_callback converts them to strings
  1016. return $this->dumpParameter(strtolower($match[1]));
  1017. } else {
  1018. $that = $this;
  1019. $replaceParameters = function ($match) use ($that) {
  1020. return "'.".$that->dumpParameter(strtolower($match[2])).".'";
  1021. };
  1022. $code = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', $replaceParameters, var_export($value, true)));
  1023. return $code;
  1024. }
  1025. } elseif (is_object($value) || is_resource($value)) {
  1026. throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.');
  1027. } else {
  1028. return var_export($value, true);
  1029. }
  1030. }
  1031. /**
  1032. * Dumps a parameter
  1033. *
  1034. * @param string $name
  1035. *
  1036. * @return string
  1037. */
  1038. public function dumpParameter($name)
  1039. {
  1040. if ($this->container->isFrozen() && $this->container->hasParameter($name)) {
  1041. return $this->dumpValue($this->container->getParameter($name), false);
  1042. }
  1043. return sprintf("\$this->getParameter('%s')", strtolower($name));
  1044. }
  1045. /**
  1046. * Gets a service call
  1047. *
  1048. * @param string $id
  1049. * @param Reference $reference
  1050. *
  1051. * @return string
  1052. */
  1053. private function getServiceCall($id, Reference $reference = null)
  1054. {
  1055. if ('service_container' === $id) {
  1056. return '$this';
  1057. }
  1058. if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) {
  1059. return sprintf('$this->get(\'%s\', ContainerInterface::NULL_ON_INVALID_REFERENCE)', $id);
  1060. } else {
  1061. if ($this->container->hasAlias($id)) {
  1062. $id = (string) $this->container->getAlias($id);
  1063. }
  1064. return sprintf('$this->get(\'%s\')', $id);
  1065. }
  1066. }
  1067. /**
  1068. * Convert a service id to a valid PHP method name.
  1069. *
  1070. * @param string $id
  1071. *
  1072. * @return string
  1073. *
  1074. * @throws InvalidArgumentException
  1075. */
  1076. private function camelize($id)
  1077. {
  1078. $name = Container::camelize($id);
  1079. if (!preg_match('/^[a-zA-Z0-9_\x7f-\xff]+$/', $name)) {
  1080. throw new InvalidArgumentException(sprintf('Service id "%s" cannot be converted to a valid PHP method name.', $id));
  1081. }
  1082. return $name;
  1083. }
  1084. /**
  1085. * Returns the next name to use
  1086. *
  1087. * @return string
  1088. */
  1089. private function getNextVariableName()
  1090. {
  1091. $firstChars = self::FIRST_CHARS;
  1092. $firstCharsLength = strlen($firstChars);
  1093. $nonFirstChars = self::NON_FIRST_CHARS;
  1094. $nonFirstCharsLength = strlen($nonFirstChars);
  1095. while (true) {
  1096. $name = '';
  1097. $i = $this->variableCount;
  1098. if ('' === $name) {
  1099. $name .= $firstChars[$i%$firstCharsLength];
  1100. $i = intval($i/$firstCharsLength);
  1101. }
  1102. while ($i > 0) {
  1103. $i -= 1;
  1104. $name .= $nonFirstChars[$i%$nonFirstCharsLength];
  1105. $i = intval($i/$nonFirstCharsLength);
  1106. }
  1107. $this->variableCount += 1;
  1108. // check that the name is not reserved
  1109. if (in_array($name, $this->reservedVariables, true)) {
  1110. continue;
  1111. }
  1112. return $name;
  1113. }
  1114. }
  1115. }