PhpDumper.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. <?php
  2. namespace Symfony\Component\DependencyInjection\Dumper;
  3. use Symfony\Component\DependencyInjection\ContainerBuilder;
  4. use Symfony\Component\DependencyInjection\Container;
  5. use Symfony\Component\DependencyInjection\ContainerInterface;
  6. use Symfony\Component\DependencyInjection\Reference;
  7. use Symfony\Component\DependencyInjection\Parameter;
  8. /*
  9. * This file is part of the Symfony framework.
  10. *
  11. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  12. *
  13. * This source file is subject to the MIT license that is bundled
  14. * with this source code in the file LICENSE.
  15. */
  16. /**
  17. * PhpDumper dumps a service container as a PHP class.
  18. *
  19. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  20. */
  21. class PhpDumper extends Dumper
  22. {
  23. /**
  24. * Dumps the service container as a PHP class.
  25. *
  26. * Available options:
  27. *
  28. * * class: The class name
  29. * * base_class: The base class name
  30. *
  31. * @param array $options An array of options
  32. *
  33. * @return string A PHP class representing of the service container
  34. */
  35. public function dump(array $options = array())
  36. {
  37. $options = array_merge(array(
  38. 'class' => 'ProjectServiceContainer',
  39. 'base_class' => 'Container',
  40. ), $options);
  41. return
  42. $this->startClass($options['class'], $options['base_class']).
  43. $this->addConstructor().
  44. $this->addServices().
  45. $this->addTags().
  46. $this->addDefaultParametersMethod().
  47. $this->endClass()
  48. ;
  49. }
  50. protected function addServiceInclude($id, $definition)
  51. {
  52. if (null !== $definition->getFile()) {
  53. return sprintf(" require_once %s;\n\n", $this->dumpValue($definition->getFile()));
  54. }
  55. }
  56. protected function addServiceShared($id, $definition)
  57. {
  58. if ($definition->isShared()) {
  59. return <<<EOF
  60. if (isset(\$this->shared['$id'])) return \$this->shared['$id'];
  61. EOF;
  62. }
  63. }
  64. protected function addServiceReturn($id, $definition)
  65. {
  66. return <<<EOF
  67. return \$instance;
  68. }
  69. EOF;
  70. }
  71. protected function addServiceInstance($id, $definition)
  72. {
  73. $class = $this->dumpValue($definition->getClass());
  74. 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)) {
  75. throw new \InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id));
  76. }
  77. $arguments = array();
  78. foreach ($definition->getArguments() as $value) {
  79. $arguments[] = $this->dumpValue($value);
  80. }
  81. if (null !== $definition->getFactoryMethod()) {
  82. if (null !== $definition->getFactoryService()) {
  83. $code = sprintf(" \$instance = %s->%s(%s);\n", $this->getServiceCall($definition->getFactoryService()), $definition->getFactoryMethod(), implode(', ', $arguments));
  84. } else {
  85. $code = sprintf(" \$instance = call_user_func(array(%s, '%s')%s);\n", $class, $definition->getFactoryMethod(), $arguments ? ', '.implode(', ', $arguments) : '');
  86. }
  87. } elseif ($class != "'".str_replace('\\', '\\\\', $definition->getClass())."'") {
  88. $code = sprintf(" \$class = %s;\n \$instance = new \$class(%s);\n", $class, implode(', ', $arguments));
  89. } else {
  90. $code = sprintf(" \$instance = new %s(%s);\n", $definition->getClass(), implode(', ', $arguments));
  91. }
  92. if ($definition->isShared()) {
  93. $code .= sprintf(" \$this->shared['$id'] = \$instance;\n");
  94. }
  95. return $code;
  96. }
  97. protected function addServiceMethodCalls($id, $definition)
  98. {
  99. $calls = '';
  100. foreach ($definition->getMethodCalls() as $call) {
  101. $arguments = array();
  102. foreach ($call[1] as $value) {
  103. $arguments[] = $this->dumpValue($value);
  104. }
  105. $calls .= $this->wrapServiceConditionals($call[1], sprintf(" \$instance->%s(%s);\n", $call[0], implode(', ', $arguments)));
  106. }
  107. return $calls;
  108. }
  109. protected function addServiceConfigurator($id, $definition)
  110. {
  111. if (!$callable = $definition->getConfigurator()) {
  112. return '';
  113. }
  114. if (is_array($callable)) {
  115. if (is_object($callable[0]) && $callable[0] instanceof Reference) {
  116. return sprintf(" %s->%s(\$instance);\n", $this->getServiceCall((string) $callable[0]), $callable[1]);
  117. } else {
  118. return sprintf(" call_user_func(array(%s, '%s'), \$instance);\n", $this->dumpValue($callable[0]), $callable[1]);
  119. }
  120. } else {
  121. return sprintf(" %s(\$instance);\n", $callable);
  122. }
  123. }
  124. protected function addService($id, $definition)
  125. {
  126. $name = Container::camelize($id);
  127. $return = '';
  128. if ($class = $definition->getClass()) {
  129. $return = sprintf("@return %s A %s instance.", 0 === strpos($class, '%') ? 'Object' : $class, $class);
  130. } elseif ($definition->getFactoryService()) {
  131. $return = sprintf('@return Object An instance returned by %s::%s().', $definition->getFactoryService(), $definition->getFactoryMethod());
  132. }
  133. $doc = '';
  134. if ($definition->isShared()) {
  135. $doc = <<<EOF
  136. *
  137. * This service is shared.
  138. * This method always returns the same instance of the service.
  139. EOF;
  140. }
  141. $code = <<<EOF
  142. /**
  143. * Gets the '$id' service.$doc
  144. *
  145. * $return
  146. */
  147. protected function get{$name}Service()
  148. {
  149. EOF;
  150. $code .=
  151. $this->addServiceInclude($id, $definition).
  152. $this->addServiceShared($id, $definition).
  153. $this->addServiceInstance($id, $definition).
  154. $this->addServiceMethodCalls($id, $definition).
  155. $this->addServiceConfigurator($id, $definition).
  156. $this->addServiceReturn($id, $definition)
  157. ;
  158. return $code;
  159. }
  160. protected function addServiceAlias($alias, $id)
  161. {
  162. $name = Container::camelize($alias);
  163. $type = 'Object';
  164. if ($this->container->hasDefinition($id)) {
  165. $class = $this->container->getDefinition($id)->getClass();
  166. $type = 0 === strpos($class, '%') ? 'Object' : $class;
  167. }
  168. return <<<EOF
  169. /**
  170. * Gets the $alias service alias.
  171. *
  172. * @return $type An instance of the $id service
  173. */
  174. protected function get{$name}Service()
  175. {
  176. return {$this->getServiceCall($id)};
  177. }
  178. EOF;
  179. }
  180. protected function addServices()
  181. {
  182. $code = '';
  183. foreach ($this->container->getDefinitions() as $id => $definition) {
  184. $code .= $this->addService($id, $definition);
  185. }
  186. foreach ($this->container->getAliases() as $alias => $id) {
  187. $code .= $this->addServiceAlias($alias, $id);
  188. }
  189. return $code;
  190. }
  191. protected function addTags()
  192. {
  193. $tags = array();
  194. foreach ($this->container->getDefinitions() as $id => $definition) {
  195. foreach ($definition->getTags() as $name => $ann) {
  196. if (!isset($tags[$name])) {
  197. $tags[$name] = array();
  198. }
  199. $tags[$name][$id] = $ann;
  200. }
  201. }
  202. $tags = var_export($tags, true);
  203. return <<<EOF
  204. /**
  205. * Returns service ids for a given tag.
  206. *
  207. * @param string \$name The tag name
  208. *
  209. * @return array An array of tags
  210. */
  211. public function findTaggedServiceIds(\$name)
  212. {
  213. static \$tags = $tags;
  214. return isset(\$tags[\$name]) ? \$tags[\$name] : array();
  215. }
  216. EOF;
  217. }
  218. protected function startClass($class, $baseClass)
  219. {
  220. $bagClass = $this->container->isFrozen() ? 'FrozenParameterBag' : 'ParameterBag';
  221. return <<<EOF
  222. <?php
  223. use Symfony\Component\DependencyInjection\ContainerInterface;
  224. use Symfony\Component\DependencyInjection\TaggedContainerInterface;
  225. use Symfony\Component\DependencyInjection\Container;
  226. use Symfony\Component\DependencyInjection\Reference;
  227. use Symfony\Component\DependencyInjection\Parameter;
  228. use Symfony\Component\DependencyInjection\ParameterBag\\$bagClass;
  229. /**
  230. * $class
  231. *
  232. * This class has been auto-generated
  233. * by the Symfony Dependency Injection Component.
  234. */
  235. class $class extends $baseClass implements TaggedContainerInterface
  236. {
  237. protected \$shared = array();
  238. EOF;
  239. }
  240. protected function addConstructor()
  241. {
  242. $bagClass = $this->container->isFrozen() ? 'FrozenParameterBag' : 'ParameterBag';
  243. return <<<EOF
  244. /**
  245. * Constructor.
  246. */
  247. public function __construct()
  248. {
  249. parent::__construct(new $bagClass(\$this->getDefaultParameters()));
  250. }
  251. EOF;
  252. }
  253. protected function addDefaultParametersMethod()
  254. {
  255. if (!$this->container->getParameterBag()->all()) {
  256. return '';
  257. }
  258. $parameters = $this->exportParameters($this->container->getParameterBag()->all());
  259. return <<<EOF
  260. /**
  261. * Gets the default parameters.
  262. *
  263. * @return array An array of the default parameters
  264. */
  265. protected function getDefaultParameters()
  266. {
  267. return $parameters;
  268. }
  269. EOF;
  270. }
  271. protected function exportParameters($parameters, $indent = 12)
  272. {
  273. $php = array();
  274. foreach ($parameters as $key => $value) {
  275. if (is_array($value)) {
  276. $value = $this->exportParameters($value, $indent + 4);
  277. } elseif ($value instanceof Reference) {
  278. throw new \InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain references to other services (reference to service %s found).', $value));
  279. } else {
  280. $value = var_export($value, true);
  281. }
  282. $php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), var_export($key, true), $value);
  283. }
  284. return sprintf("array(\n%s\n%s)", implode("\n", $php), str_repeat(' ', $indent - 4));
  285. }
  286. protected function endClass()
  287. {
  288. return <<<EOF
  289. }
  290. EOF;
  291. }
  292. protected function wrapServiceConditionals($value, $code)
  293. {
  294. if (!$services = ContainerBuilder::getServiceConditionals($value)) {
  295. return $code;
  296. }
  297. $conditions = array();
  298. foreach ($services as $service) {
  299. $conditions[] = sprintf("\$this->has('%s')", $service);
  300. }
  301. // re-indent the wrapped code
  302. $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code)));
  303. return sprintf(" if (%s) {\n%s }\n", implode(' && ', $conditions), $code);
  304. }
  305. protected function dumpValue($value, $interpolate = true)
  306. {
  307. if (is_array($value)) {
  308. $code = array();
  309. foreach ($value as $k => $v) {
  310. $code[] = sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate));
  311. }
  312. return sprintf('array(%s)', implode(', ', $code));
  313. } elseif (is_object($value) && $value instanceof Reference) {
  314. return $this->getServiceCall((string) $value, $value);
  315. } elseif (is_object($value) && $value instanceof Parameter) {
  316. return $this->dumpParameter($value);
  317. } elseif (true === $interpolate && is_string($value)) {
  318. if (preg_match('/^%([^%]+)%$/', $value, $match)) {
  319. // we do this to deal with non string values (boolean, integer, ...)
  320. // the preg_replace_callback converts them to strings
  321. return $this->dumpParameter(strtolower($match[1]));
  322. } else {
  323. $that = $this;
  324. $replaceParameters = function ($match) use ($that)
  325. {
  326. return sprintf("'.".$that->dumpParameter(strtolower($match[2])).".'");
  327. };
  328. $code = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', $replaceParameters, var_export($value, true)));
  329. // optimize string
  330. $code = preg_replace(array("/^''\./", "/\.''$/", "/\.''\./"), array('', '', '.'), $code);
  331. return $code;
  332. }
  333. } elseif (is_object($value) || is_resource($value)) {
  334. throw new \RuntimeException('Unable to dump a service container if a parameter is an object or a resource.');
  335. } else {
  336. return var_export($value, true);
  337. }
  338. }
  339. public function dumpParameter($name)
  340. {
  341. if ($this->container->isFrozen() && $this->container->hasParameter($name)) {
  342. return $this->dumpValue($this->container->getParameter($name), false);
  343. }
  344. return sprintf("\$this->getParameter('%s')", strtolower($name));
  345. }
  346. protected function getServiceCall($id, Reference $reference = null)
  347. {
  348. if ('service_container' === $id) {
  349. return '$this';
  350. }
  351. if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) {
  352. return sprintf('$this->get(\'%s\', ContainerInterface::NULL_ON_INVALID_REFERENCE)', $id);
  353. } else {
  354. if ($this->container->hasAlias($id)) {
  355. $id = $this->container->getAlias($id);
  356. }
  357. return sprintf('$this->get(\'%s\')', $id);
  358. }
  359. }
  360. }