XmlDescriptor.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  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\Bundle\FrameworkBundle\Console\Descriptor;
  11. use Symfony\Component\DependencyInjection\Alias;
  12. use Symfony\Component\DependencyInjection\ContainerBuilder;
  13. use Symfony\Component\DependencyInjection\Definition;
  14. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
  15. use Symfony\Component\DependencyInjection\Reference;
  16. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  17. use Symfony\Component\Routing\Route;
  18. use Symfony\Component\Routing\RouteCollection;
  19. /**
  20. * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
  21. *
  22. * @internal
  23. */
  24. class XmlDescriptor extends Descriptor
  25. {
  26. /**
  27. * {@inheritdoc}
  28. */
  29. protected function describeRouteCollection(RouteCollection $routes, array $options = array())
  30. {
  31. $this->writeDocument($this->getRouteCollectionDocument($routes));
  32. }
  33. /**
  34. * {@inheritdoc}
  35. */
  36. protected function describeRoute(Route $route, array $options = array())
  37. {
  38. $this->writeDocument($this->getRouteDocument($route, isset($options['name']) ? $options['name'] : null));
  39. }
  40. /**
  41. * {@inheritdoc}
  42. */
  43. protected function describeContainerParameters(ParameterBag $parameters, array $options = array())
  44. {
  45. $this->writeDocument($this->getContainerParametersDocument($parameters));
  46. }
  47. /**
  48. * {@inheritdoc}
  49. */
  50. protected function describeContainerTags(ContainerBuilder $builder, array $options = array())
  51. {
  52. $this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_private']) && $options['show_private']));
  53. }
  54. /**
  55. * {@inheritdoc}
  56. */
  57. protected function describeContainerService($service, array $options = array())
  58. {
  59. if (!isset($options['id'])) {
  60. throw new \InvalidArgumentException('An "id" option must be provided.');
  61. }
  62. $this->writeDocument($this->getContainerServiceDocument($service, $options['id']));
  63. }
  64. /**
  65. * {@inheritdoc}
  66. */
  67. protected function describeContainerServices(ContainerBuilder $builder, array $options = array())
  68. {
  69. $this->writeDocument($this->getContainerServicesDocument($builder, isset($options['tag']) ? $options['tag'] : null, isset($options['show_private']) && $options['show_private']));
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. protected function describeContainerDefinition(Definition $definition, array $options = array())
  75. {
  76. $this->writeDocument($this->getContainerDefinitionDocument($definition, isset($options['id']) ? $options['id'] : null, isset($options['omit_tags']) && $options['omit_tags']));
  77. }
  78. /**
  79. * {@inheritdoc}
  80. */
  81. protected function describeContainerAlias(Alias $alias, array $options = array())
  82. {
  83. $this->writeDocument($this->getContainerAliasDocument($alias, isset($options['id']) ? $options['id'] : null));
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = array())
  89. {
  90. $this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, array_key_exists('event', $options) ? $options['event'] : null));
  91. }
  92. /**
  93. * {@inheritdoc}
  94. */
  95. protected function describeCallable($callable, array $options = array())
  96. {
  97. $this->writeDocument($this->getCallableDocument($callable));
  98. }
  99. /**
  100. * {@inheritdoc}
  101. */
  102. protected function describeContainerParameter($parameter, array $options = array())
  103. {
  104. $this->writeDocument($this->getContainerParameterDocument($parameter, $options));
  105. }
  106. /**
  107. * Writes DOM document.
  108. *
  109. * @return \DOMDocument|string
  110. */
  111. private function writeDocument(\DOMDocument $dom)
  112. {
  113. $dom->formatOutput = true;
  114. $this->write($dom->saveXML());
  115. }
  116. /**
  117. * @return \DOMDocument
  118. */
  119. private function getRouteCollectionDocument(RouteCollection $routes)
  120. {
  121. $dom = new \DOMDocument('1.0', 'UTF-8');
  122. $dom->appendChild($routesXML = $dom->createElement('routes'));
  123. foreach ($routes->all() as $name => $route) {
  124. $routeXML = $this->getRouteDocument($route, $name);
  125. $routesXML->appendChild($routesXML->ownerDocument->importNode($routeXML->childNodes->item(0), true));
  126. }
  127. return $dom;
  128. }
  129. /**
  130. * @param Route $route
  131. * @param string|null $name
  132. *
  133. * @return \DOMDocument
  134. */
  135. private function getRouteDocument(Route $route, $name = null)
  136. {
  137. $dom = new \DOMDocument('1.0', 'UTF-8');
  138. $dom->appendChild($routeXML = $dom->createElement('route'));
  139. if ($name) {
  140. $routeXML->setAttribute('name', $name);
  141. }
  142. $routeXML->setAttribute('class', \get_class($route));
  143. $routeXML->appendChild($pathXML = $dom->createElement('path'));
  144. $pathXML->setAttribute('regex', $route->compile()->getRegex());
  145. $pathXML->appendChild(new \DOMText($route->getPath()));
  146. if ('' !== $route->getHost()) {
  147. $routeXML->appendChild($hostXML = $dom->createElement('host'));
  148. $hostXML->setAttribute('regex', $route->compile()->getHostRegex());
  149. $hostXML->appendChild(new \DOMText($route->getHost()));
  150. }
  151. foreach ($route->getSchemes() as $scheme) {
  152. $routeXML->appendChild($schemeXML = $dom->createElement('scheme'));
  153. $schemeXML->appendChild(new \DOMText($scheme));
  154. }
  155. foreach ($route->getMethods() as $method) {
  156. $routeXML->appendChild($methodXML = $dom->createElement('method'));
  157. $methodXML->appendChild(new \DOMText($method));
  158. }
  159. if ($route->getDefaults()) {
  160. $routeXML->appendChild($defaultsXML = $dom->createElement('defaults'));
  161. foreach ($route->getDefaults() as $attribute => $value) {
  162. $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
  163. $defaultXML->setAttribute('key', $attribute);
  164. $defaultXML->appendChild(new \DOMText($this->formatValue($value)));
  165. }
  166. }
  167. $requirements = $route->getRequirements();
  168. unset($requirements['_scheme'], $requirements['_method']);
  169. if ($requirements) {
  170. $routeXML->appendChild($requirementsXML = $dom->createElement('requirements'));
  171. foreach ($requirements as $attribute => $pattern) {
  172. $requirementsXML->appendChild($requirementXML = $dom->createElement('requirement'));
  173. $requirementXML->setAttribute('key', $attribute);
  174. $requirementXML->appendChild(new \DOMText($pattern));
  175. }
  176. }
  177. if ($route->getOptions()) {
  178. $routeXML->appendChild($optionsXML = $dom->createElement('options'));
  179. foreach ($route->getOptions() as $name => $value) {
  180. $optionsXML->appendChild($optionXML = $dom->createElement('option'));
  181. $optionXML->setAttribute('key', $name);
  182. $optionXML->appendChild(new \DOMText($this->formatValue($value)));
  183. }
  184. }
  185. return $dom;
  186. }
  187. /**
  188. * @return \DOMDocument
  189. */
  190. private function getContainerParametersDocument(ParameterBag $parameters)
  191. {
  192. $dom = new \DOMDocument('1.0', 'UTF-8');
  193. $dom->appendChild($parametersXML = $dom->createElement('parameters'));
  194. foreach ($this->sortParameters($parameters) as $key => $value) {
  195. $parametersXML->appendChild($parameterXML = $dom->createElement('parameter'));
  196. $parameterXML->setAttribute('key', $key);
  197. $parameterXML->appendChild(new \DOMText($this->formatParameter($value)));
  198. }
  199. return $dom;
  200. }
  201. /**
  202. * @param ContainerBuilder $builder
  203. * @param bool $showPrivate
  204. *
  205. * @return \DOMDocument
  206. */
  207. private function getContainerTagsDocument(ContainerBuilder $builder, $showPrivate = false)
  208. {
  209. $dom = new \DOMDocument('1.0', 'UTF-8');
  210. $dom->appendChild($containerXML = $dom->createElement('container'));
  211. foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) {
  212. $containerXML->appendChild($tagXML = $dom->createElement('tag'));
  213. $tagXML->setAttribute('name', $tag);
  214. foreach ($definitions as $serviceId => $definition) {
  215. $definitionXML = $this->getContainerDefinitionDocument($definition, $serviceId, true);
  216. $tagXML->appendChild($dom->importNode($definitionXML->childNodes->item(0), true));
  217. }
  218. }
  219. return $dom;
  220. }
  221. /**
  222. * @param mixed $service
  223. * @param string $id
  224. *
  225. * @return \DOMDocument
  226. */
  227. private function getContainerServiceDocument($service, $id)
  228. {
  229. $dom = new \DOMDocument('1.0', 'UTF-8');
  230. if ($service instanceof Alias) {
  231. $dom->appendChild($dom->importNode($this->getContainerAliasDocument($service, $id)->childNodes->item(0), true));
  232. } elseif ($service instanceof Definition) {
  233. $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($service, $id)->childNodes->item(0), true));
  234. } else {
  235. $dom->appendChild($serviceXML = $dom->createElement('service'));
  236. $serviceXML->setAttribute('id', $id);
  237. $serviceXML->setAttribute('class', \get_class($service));
  238. }
  239. return $dom;
  240. }
  241. /**
  242. * @param ContainerBuilder $builder
  243. * @param string|null $tag
  244. * @param bool $showPrivate
  245. *
  246. * @return \DOMDocument
  247. */
  248. private function getContainerServicesDocument(ContainerBuilder $builder, $tag = null, $showPrivate = false)
  249. {
  250. $dom = new \DOMDocument('1.0', 'UTF-8');
  251. $dom->appendChild($containerXML = $dom->createElement('container'));
  252. $serviceIds = $tag ? array_keys($builder->findTaggedServiceIds($tag)) : $builder->getServiceIds();
  253. foreach ($this->sortServiceIds($serviceIds) as $serviceId) {
  254. $service = $this->resolveServiceDefinition($builder, $serviceId);
  255. if ($service instanceof Definition && !($showPrivate || $service->isPublic())) {
  256. continue;
  257. }
  258. $serviceXML = $this->getContainerServiceDocument($service, $serviceId);
  259. $containerXML->appendChild($containerXML->ownerDocument->importNode($serviceXML->childNodes->item(0), true));
  260. }
  261. return $dom;
  262. }
  263. /**
  264. * @param Definition $definition
  265. * @param string|null $id
  266. * @param bool $omitTags
  267. *
  268. * @return \DOMDocument
  269. */
  270. private function getContainerDefinitionDocument(Definition $definition, $id = null, $omitTags = false)
  271. {
  272. $dom = new \DOMDocument('1.0', 'UTF-8');
  273. $dom->appendChild($serviceXML = $dom->createElement('definition'));
  274. if ($id) {
  275. $serviceXML->setAttribute('id', $id);
  276. }
  277. $serviceXML->setAttribute('class', $definition->getClass());
  278. if ($definition->getFactoryClass(false)) {
  279. $serviceXML->setAttribute('factory-class', $definition->getFactoryClass(false));
  280. }
  281. if ($definition->getFactoryService(false)) {
  282. $serviceXML->setAttribute('factory-service', $definition->getFactoryService(false));
  283. }
  284. if ($definition->getFactoryMethod(false)) {
  285. $serviceXML->setAttribute('factory-method', $definition->getFactoryMethod(false));
  286. }
  287. if ($factory = $definition->getFactory()) {
  288. $serviceXML->appendChild($factoryXML = $dom->createElement('factory'));
  289. if (\is_array($factory)) {
  290. if ($factory[0] instanceof Reference) {
  291. $factoryXML->setAttribute('service', (string) $factory[0]);
  292. } elseif ($factory[0] instanceof Definition) {
  293. throw new \InvalidArgumentException('Factory is not describable.');
  294. } else {
  295. $factoryXML->setAttribute('class', $factory[0]);
  296. }
  297. $factoryXML->setAttribute('method', $factory[1]);
  298. } else {
  299. $factoryXML->setAttribute('function', $factory);
  300. }
  301. }
  302. $serviceXML->setAttribute('scope', $definition->getScope(false));
  303. $serviceXML->setAttribute('public', $definition->isPublic() ? 'true' : 'false');
  304. $serviceXML->setAttribute('synthetic', $definition->isSynthetic() ? 'true' : 'false');
  305. $serviceXML->setAttribute('lazy', $definition->isLazy() ? 'true' : 'false');
  306. $serviceXML->setAttribute('shared', $definition->isShared() ? 'true' : 'false');
  307. $serviceXML->setAttribute('synchronized', $definition->isSynchronized(false) ? 'true' : 'false');
  308. $serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false');
  309. $serviceXML->setAttribute('autowired', $definition->isAutowired() ? 'true' : 'false');
  310. $serviceXML->setAttribute('file', $definition->getFile());
  311. if (!$omitTags) {
  312. if ($tags = $definition->getTags()) {
  313. $serviceXML->appendChild($tagsXML = $dom->createElement('tags'));
  314. foreach ($tags as $tagName => $tagData) {
  315. foreach ($tagData as $parameters) {
  316. $tagsXML->appendChild($tagXML = $dom->createElement('tag'));
  317. $tagXML->setAttribute('name', $tagName);
  318. foreach ($parameters as $name => $value) {
  319. $tagXML->appendChild($parameterXML = $dom->createElement('parameter'));
  320. $parameterXML->setAttribute('name', $name);
  321. $parameterXML->appendChild(new \DOMText($this->formatParameter($value)));
  322. }
  323. }
  324. }
  325. }
  326. }
  327. return $dom;
  328. }
  329. /**
  330. * @param Alias $alias
  331. * @param string|null $id
  332. *
  333. * @return \DOMDocument
  334. */
  335. private function getContainerAliasDocument(Alias $alias, $id = null)
  336. {
  337. $dom = new \DOMDocument('1.0', 'UTF-8');
  338. $dom->appendChild($aliasXML = $dom->createElement('alias'));
  339. if ($id) {
  340. $aliasXML->setAttribute('id', $id);
  341. }
  342. $aliasXML->setAttribute('service', (string) $alias);
  343. $aliasXML->setAttribute('public', $alias->isPublic() ? 'true' : 'false');
  344. return $dom;
  345. }
  346. /**
  347. * @return \DOMDocument
  348. */
  349. private function getContainerParameterDocument($parameter, $options = array())
  350. {
  351. $dom = new \DOMDocument('1.0', 'UTF-8');
  352. $dom->appendChild($parameterXML = $dom->createElement('parameter'));
  353. if (isset($options['parameter'])) {
  354. $parameterXML->setAttribute('key', $options['parameter']);
  355. }
  356. $parameterXML->appendChild(new \DOMText($this->formatParameter($parameter)));
  357. return $dom;
  358. }
  359. /**
  360. * @param EventDispatcherInterface $eventDispatcher
  361. * @param string|null $event
  362. *
  363. * @return \DOMDocument
  364. */
  365. private function getEventDispatcherListenersDocument(EventDispatcherInterface $eventDispatcher, $event = null)
  366. {
  367. $dom = new \DOMDocument('1.0', 'UTF-8');
  368. $dom->appendChild($eventDispatcherXML = $dom->createElement('event-dispatcher'));
  369. $registeredListeners = $eventDispatcher->getListeners($event);
  370. if (null !== $event) {
  371. $this->appendEventListenerDocument($eventDispatcher, $event, $eventDispatcherXML, $registeredListeners);
  372. } else {
  373. ksort($registeredListeners);
  374. foreach ($registeredListeners as $eventListened => $eventListeners) {
  375. $eventDispatcherXML->appendChild($eventXML = $dom->createElement('event'));
  376. $eventXML->setAttribute('name', $eventListened);
  377. $this->appendEventListenerDocument($eventDispatcher, $eventListened, $eventXML, $eventListeners);
  378. }
  379. }
  380. return $dom;
  381. }
  382. /**
  383. * @param \DOMElement $element
  384. * @param array $eventListeners
  385. */
  386. private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, $event, \DOMElement $element, array $eventListeners)
  387. {
  388. foreach ($eventListeners as $listener) {
  389. $callableXML = $this->getCallableDocument($listener);
  390. $callableXML->childNodes->item(0)->setAttribute('priority', $eventDispatcher->getListenerPriority($event, $listener));
  391. $element->appendChild($element->ownerDocument->importNode($callableXML->childNodes->item(0), true));
  392. }
  393. }
  394. /**
  395. * @param callable $callable
  396. *
  397. * @return \DOMDocument
  398. */
  399. private function getCallableDocument($callable)
  400. {
  401. $dom = new \DOMDocument('1.0', 'UTF-8');
  402. $dom->appendChild($callableXML = $dom->createElement('callable'));
  403. if (\is_array($callable)) {
  404. $callableXML->setAttribute('type', 'function');
  405. if (\is_object($callable[0])) {
  406. $callableXML->setAttribute('name', $callable[1]);
  407. $callableXML->setAttribute('class', \get_class($callable[0]));
  408. } else {
  409. if (0 !== strpos($callable[1], 'parent::')) {
  410. $callableXML->setAttribute('name', $callable[1]);
  411. $callableXML->setAttribute('class', $callable[0]);
  412. $callableXML->setAttribute('static', 'true');
  413. } else {
  414. $callableXML->setAttribute('name', substr($callable[1], 8));
  415. $callableXML->setAttribute('class', $callable[0]);
  416. $callableXML->setAttribute('static', 'true');
  417. $callableXML->setAttribute('parent', 'true');
  418. }
  419. }
  420. return $dom;
  421. }
  422. if (\is_string($callable)) {
  423. $callableXML->setAttribute('type', 'function');
  424. if (false === strpos($callable, '::')) {
  425. $callableXML->setAttribute('name', $callable);
  426. } else {
  427. $callableParts = explode('::', $callable);
  428. $callableXML->setAttribute('name', $callableParts[1]);
  429. $callableXML->setAttribute('class', $callableParts[0]);
  430. $callableXML->setAttribute('static', 'true');
  431. }
  432. return $dom;
  433. }
  434. if ($callable instanceof \Closure) {
  435. $callableXML->setAttribute('type', 'closure');
  436. return $dom;
  437. }
  438. if (method_exists($callable, '__invoke')) {
  439. $callableXML->setAttribute('type', 'object');
  440. $callableXML->setAttribute('name', \get_class($callable));
  441. return $dom;
  442. }
  443. throw new \InvalidArgumentException('Callable is not describable.');
  444. }
  445. }