XmlDescriptor.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  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\Console\Exception\LogicException;
  12. use Symfony\Component\Console\Exception\RuntimeException;
  13. use Symfony\Component\DependencyInjection\Alias;
  14. use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
  15. use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
  16. use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
  17. use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
  18. use Symfony\Component\DependencyInjection\ContainerBuilder;
  19. use Symfony\Component\DependencyInjection\Definition;
  20. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
  21. use Symfony\Component\DependencyInjection\Reference;
  22. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  23. use Symfony\Component\Routing\Route;
  24. use Symfony\Component\Routing\RouteCollection;
  25. /**
  26. * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
  27. *
  28. * @internal
  29. */
  30. class XmlDescriptor extends Descriptor
  31. {
  32. protected function describeRouteCollection(RouteCollection $routes, array $options = [])
  33. {
  34. $this->writeDocument($this->getRouteCollectionDocument($routes));
  35. }
  36. protected function describeRoute(Route $route, array $options = [])
  37. {
  38. $this->writeDocument($this->getRouteDocument($route, $options['name'] ?? null));
  39. }
  40. protected function describeContainerParameters(ParameterBag $parameters, array $options = [])
  41. {
  42. $this->writeDocument($this->getContainerParametersDocument($parameters));
  43. }
  44. protected function describeContainerTags(ContainerBuilder $builder, array $options = [])
  45. {
  46. $this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_hidden']) && $options['show_hidden']));
  47. }
  48. protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null)
  49. {
  50. if (!isset($options['id'])) {
  51. throw new \InvalidArgumentException('An "id" option must be provided.');
  52. }
  53. $this->writeDocument($this->getContainerServiceDocument($service, $options['id'], $builder, isset($options['show_arguments']) && $options['show_arguments']));
  54. }
  55. protected function describeContainerServices(ContainerBuilder $builder, array $options = [])
  56. {
  57. $this->writeDocument($this->getContainerServicesDocument($builder, $options['tag'] ?? null, isset($options['show_hidden']) && $options['show_hidden'], isset($options['show_arguments']) && $options['show_arguments'], $options['filter'] ?? null));
  58. }
  59. protected function describeContainerDefinition(Definition $definition, array $options = [])
  60. {
  61. $this->writeDocument($this->getContainerDefinitionDocument($definition, $options['id'] ?? null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']));
  62. }
  63. protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null)
  64. {
  65. $dom = new \DOMDocument('1.0', 'UTF-8');
  66. $dom->appendChild($dom->importNode($this->getContainerAliasDocument($alias, $options['id'] ?? null)->childNodes->item(0), true));
  67. if (!$builder) {
  68. $this->writeDocument($dom);
  69. return;
  70. }
  71. $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $alias), (string) $alias)->childNodes->item(0), true));
  72. $this->writeDocument($dom);
  73. }
  74. protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = [])
  75. {
  76. $this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, \array_key_exists('event', $options) ? $options['event'] : null));
  77. }
  78. protected function describeCallable($callable, array $options = [])
  79. {
  80. $this->writeDocument($this->getCallableDocument($callable));
  81. }
  82. protected function describeContainerParameter($parameter, array $options = [])
  83. {
  84. $this->writeDocument($this->getContainerParameterDocument($parameter, $options));
  85. }
  86. protected function describeContainerEnvVars(array $envs, array $options = [])
  87. {
  88. throw new LogicException('Using the XML format to debug environment variables is not supported.');
  89. }
  90. protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void
  91. {
  92. $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class'));
  93. if (!file_exists($containerDeprecationFilePath)) {
  94. throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.');
  95. }
  96. $logs = unserialize(file_get_contents($containerDeprecationFilePath));
  97. $dom = new \DOMDocument('1.0', 'UTF-8');
  98. $dom->appendChild($deprecationsXML = $dom->createElement('deprecations'));
  99. $formattedLogs = [];
  100. $remainingCount = 0;
  101. foreach ($logs as $log) {
  102. $deprecationsXML->appendChild($deprecationXML = $dom->createElement('deprecation'));
  103. $deprecationXML->setAttribute('count', $log['count']);
  104. $deprecationXML->appendChild($dom->createElement('message', $log['message']));
  105. $deprecationXML->appendChild($dom->createElement('file', $log['file']));
  106. $deprecationXML->appendChild($dom->createElement('line', $log['line']));
  107. $remainingCount += $log['count'];
  108. }
  109. $deprecationsXML->setAttribute('remainingCount', $remainingCount);
  110. $this->writeDocument($dom);
  111. }
  112. private function writeDocument(\DOMDocument $dom)
  113. {
  114. $dom->formatOutput = true;
  115. $this->write($dom->saveXML());
  116. }
  117. private function getRouteCollectionDocument(RouteCollection $routes): \DOMDocument
  118. {
  119. $dom = new \DOMDocument('1.0', 'UTF-8');
  120. $dom->appendChild($routesXML = $dom->createElement('routes'));
  121. foreach ($routes->all() as $name => $route) {
  122. $routeXML = $this->getRouteDocument($route, $name);
  123. $routesXML->appendChild($routesXML->ownerDocument->importNode($routeXML->childNodes->item(0), true));
  124. }
  125. return $dom;
  126. }
  127. private function getRouteDocument(Route $route, string $name = null): \DOMDocument
  128. {
  129. $dom = new \DOMDocument('1.0', 'UTF-8');
  130. $dom->appendChild($routeXML = $dom->createElement('route'));
  131. if ($name) {
  132. $routeXML->setAttribute('name', $name);
  133. }
  134. $routeXML->setAttribute('class', \get_class($route));
  135. $routeXML->appendChild($pathXML = $dom->createElement('path'));
  136. $pathXML->setAttribute('regex', $route->compile()->getRegex());
  137. $pathXML->appendChild(new \DOMText($route->getPath()));
  138. if ('' !== $route->getHost()) {
  139. $routeXML->appendChild($hostXML = $dom->createElement('host'));
  140. $hostXML->setAttribute('regex', $route->compile()->getHostRegex());
  141. $hostXML->appendChild(new \DOMText($route->getHost()));
  142. }
  143. foreach ($route->getSchemes() as $scheme) {
  144. $routeXML->appendChild($schemeXML = $dom->createElement('scheme'));
  145. $schemeXML->appendChild(new \DOMText($scheme));
  146. }
  147. foreach ($route->getMethods() as $method) {
  148. $routeXML->appendChild($methodXML = $dom->createElement('method'));
  149. $methodXML->appendChild(new \DOMText($method));
  150. }
  151. if ($route->getDefaults()) {
  152. $routeXML->appendChild($defaultsXML = $dom->createElement('defaults'));
  153. foreach ($route->getDefaults() as $attribute => $value) {
  154. $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
  155. $defaultXML->setAttribute('key', $attribute);
  156. $defaultXML->appendChild(new \DOMText($this->formatValue($value)));
  157. }
  158. }
  159. $originRequirements = $requirements = $route->getRequirements();
  160. unset($requirements['_scheme'], $requirements['_method']);
  161. if ($requirements) {
  162. $routeXML->appendChild($requirementsXML = $dom->createElement('requirements'));
  163. foreach ($originRequirements as $attribute => $pattern) {
  164. $requirementsXML->appendChild($requirementXML = $dom->createElement('requirement'));
  165. $requirementXML->setAttribute('key', $attribute);
  166. $requirementXML->appendChild(new \DOMText($pattern));
  167. }
  168. }
  169. if ($route->getOptions()) {
  170. $routeXML->appendChild($optionsXML = $dom->createElement('options'));
  171. foreach ($route->getOptions() as $name => $value) {
  172. $optionsXML->appendChild($optionXML = $dom->createElement('option'));
  173. $optionXML->setAttribute('key', $name);
  174. $optionXML->appendChild(new \DOMText($this->formatValue($value)));
  175. }
  176. }
  177. if ('' !== $route->getCondition()) {
  178. $routeXML->appendChild($conditionXML = $dom->createElement('condition'));
  179. $conditionXML->appendChild(new \DOMText($route->getCondition()));
  180. }
  181. return $dom;
  182. }
  183. private function getContainerParametersDocument(ParameterBag $parameters): \DOMDocument
  184. {
  185. $dom = new \DOMDocument('1.0', 'UTF-8');
  186. $dom->appendChild($parametersXML = $dom->createElement('parameters'));
  187. foreach ($this->sortParameters($parameters) as $key => $value) {
  188. $parametersXML->appendChild($parameterXML = $dom->createElement('parameter'));
  189. $parameterXML->setAttribute('key', $key);
  190. $parameterXML->appendChild(new \DOMText($this->formatParameter($value)));
  191. }
  192. return $dom;
  193. }
  194. private function getContainerTagsDocument(ContainerBuilder $builder, bool $showHidden = false): \DOMDocument
  195. {
  196. $dom = new \DOMDocument('1.0', 'UTF-8');
  197. $dom->appendChild($containerXML = $dom->createElement('container'));
  198. foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) {
  199. $containerXML->appendChild($tagXML = $dom->createElement('tag'));
  200. $tagXML->setAttribute('name', $tag);
  201. foreach ($definitions as $serviceId => $definition) {
  202. $definitionXML = $this->getContainerDefinitionDocument($definition, $serviceId, true);
  203. $tagXML->appendChild($dom->importNode($definitionXML->childNodes->item(0), true));
  204. }
  205. }
  206. return $dom;
  207. }
  208. private function getContainerServiceDocument($service, string $id, ContainerBuilder $builder = null, bool $showArguments = false): \DOMDocument
  209. {
  210. $dom = new \DOMDocument('1.0', 'UTF-8');
  211. if ($service instanceof Alias) {
  212. $dom->appendChild($dom->importNode($this->getContainerAliasDocument($service, $id)->childNodes->item(0), true));
  213. if ($builder) {
  214. $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $service), (string) $service, false, $showArguments)->childNodes->item(0), true));
  215. }
  216. } elseif ($service instanceof Definition) {
  217. $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($service, $id, false, $showArguments)->childNodes->item(0), true));
  218. } else {
  219. $dom->appendChild($serviceXML = $dom->createElement('service'));
  220. $serviceXML->setAttribute('id', $id);
  221. $serviceXML->setAttribute('class', \get_class($service));
  222. }
  223. return $dom;
  224. }
  225. private function getContainerServicesDocument(ContainerBuilder $builder, string $tag = null, bool $showHidden = false, bool $showArguments = false, callable $filter = null): \DOMDocument
  226. {
  227. $dom = new \DOMDocument('1.0', 'UTF-8');
  228. $dom->appendChild($containerXML = $dom->createElement('container'));
  229. $serviceIds = $tag
  230. ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($tag))
  231. : $this->sortServiceIds($builder->getServiceIds());
  232. if ($filter) {
  233. $serviceIds = array_filter($serviceIds, $filter);
  234. }
  235. foreach ($serviceIds as $serviceId) {
  236. $service = $this->resolveServiceDefinition($builder, $serviceId);
  237. if ($showHidden xor '.' === ($serviceId[0] ?? null)) {
  238. continue;
  239. }
  240. $serviceXML = $this->getContainerServiceDocument($service, $serviceId, null, $showArguments);
  241. $containerXML->appendChild($containerXML->ownerDocument->importNode($serviceXML->childNodes->item(0), true));
  242. }
  243. return $dom;
  244. }
  245. private function getContainerDefinitionDocument(Definition $definition, string $id = null, bool $omitTags = false, bool $showArguments = false): \DOMDocument
  246. {
  247. $dom = new \DOMDocument('1.0', 'UTF-8');
  248. $dom->appendChild($serviceXML = $dom->createElement('definition'));
  249. if ($id) {
  250. $serviceXML->setAttribute('id', $id);
  251. }
  252. if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) {
  253. $serviceXML->appendChild($descriptionXML = $dom->createElement('description'));
  254. $descriptionXML->appendChild($dom->createCDATASection($classDescription));
  255. }
  256. $serviceXML->setAttribute('class', $definition->getClass());
  257. if ($factory = $definition->getFactory()) {
  258. $serviceXML->appendChild($factoryXML = $dom->createElement('factory'));
  259. if (\is_array($factory)) {
  260. if ($factory[0] instanceof Reference) {
  261. $factoryXML->setAttribute('service', (string) $factory[0]);
  262. } elseif ($factory[0] instanceof Definition) {
  263. throw new \InvalidArgumentException('Factory is not describable.');
  264. } else {
  265. $factoryXML->setAttribute('class', $factory[0]);
  266. }
  267. $factoryXML->setAttribute('method', $factory[1]);
  268. } else {
  269. $factoryXML->setAttribute('function', $factory);
  270. }
  271. }
  272. $serviceXML->setAttribute('public', $definition->isPublic() && !$definition->isPrivate() ? 'true' : 'false');
  273. $serviceXML->setAttribute('synthetic', $definition->isSynthetic() ? 'true' : 'false');
  274. $serviceXML->setAttribute('lazy', $definition->isLazy() ? 'true' : 'false');
  275. $serviceXML->setAttribute('shared', $definition->isShared() ? 'true' : 'false');
  276. $serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false');
  277. $serviceXML->setAttribute('autowired', $definition->isAutowired() ? 'true' : 'false');
  278. $serviceXML->setAttribute('autoconfigured', $definition->isAutoconfigured() ? 'true' : 'false');
  279. $serviceXML->setAttribute('file', $definition->getFile());
  280. $calls = $definition->getMethodCalls();
  281. if (\count($calls) > 0) {
  282. $serviceXML->appendChild($callsXML = $dom->createElement('calls'));
  283. foreach ($calls as $callData) {
  284. $callsXML->appendChild($callXML = $dom->createElement('call'));
  285. $callXML->setAttribute('method', $callData[0]);
  286. if ($callData[2] ?? false) {
  287. $callXML->setAttribute('returns-clone', 'true');
  288. }
  289. }
  290. }
  291. if ($showArguments) {
  292. foreach ($this->getArgumentNodes($definition->getArguments(), $dom) as $node) {
  293. $serviceXML->appendChild($node);
  294. }
  295. }
  296. if (!$omitTags) {
  297. if ($tags = $this->sortTagsByPriority($definition->getTags())) {
  298. $serviceXML->appendChild($tagsXML = $dom->createElement('tags'));
  299. foreach ($tags as $tagName => $tagData) {
  300. foreach ($tagData as $parameters) {
  301. $tagsXML->appendChild($tagXML = $dom->createElement('tag'));
  302. $tagXML->setAttribute('name', $tagName);
  303. foreach ($parameters as $name => $value) {
  304. $tagXML->appendChild($parameterXML = $dom->createElement('parameter'));
  305. $parameterXML->setAttribute('name', $name);
  306. $parameterXML->appendChild(new \DOMText($this->formatParameter($value)));
  307. }
  308. }
  309. }
  310. }
  311. }
  312. return $dom;
  313. }
  314. /**
  315. * @return \DOMNode[]
  316. */
  317. private function getArgumentNodes(array $arguments, \DOMDocument $dom): array
  318. {
  319. $nodes = [];
  320. foreach ($arguments as $argumentKey => $argument) {
  321. $argumentXML = $dom->createElement('argument');
  322. if (\is_string($argumentKey)) {
  323. $argumentXML->setAttribute('key', $argumentKey);
  324. }
  325. if ($argument instanceof ServiceClosureArgument) {
  326. $argument = $argument->getValues()[0];
  327. }
  328. if ($argument instanceof Reference) {
  329. $argumentXML->setAttribute('type', 'service');
  330. $argumentXML->setAttribute('id', (string) $argument);
  331. } elseif ($argument instanceof IteratorArgument || $argument instanceof ServiceLocatorArgument) {
  332. $argumentXML->setAttribute('type', $argument instanceof IteratorArgument ? 'iterator' : 'service_locator');
  333. foreach ($this->getArgumentNodes($argument->getValues(), $dom) as $childArgumentXML) {
  334. $argumentXML->appendChild($childArgumentXML);
  335. }
  336. } elseif ($argument instanceof Definition) {
  337. $argumentXML->appendChild($dom->importNode($this->getContainerDefinitionDocument($argument, null, false, true)->childNodes->item(0), true));
  338. } elseif ($argument instanceof AbstractArgument) {
  339. $argumentXML->setAttribute('type', 'abstract');
  340. $argumentXML->appendChild(new \DOMText($argument->getText()));
  341. } elseif (\is_array($argument)) {
  342. $argumentXML->setAttribute('type', 'collection');
  343. foreach ($this->getArgumentNodes($argument, $dom) as $childArgumentXML) {
  344. $argumentXML->appendChild($childArgumentXML);
  345. }
  346. } else {
  347. $argumentXML->appendChild(new \DOMText($argument));
  348. }
  349. $nodes[] = $argumentXML;
  350. }
  351. return $nodes;
  352. }
  353. private function getContainerAliasDocument(Alias $alias, string $id = null): \DOMDocument
  354. {
  355. $dom = new \DOMDocument('1.0', 'UTF-8');
  356. $dom->appendChild($aliasXML = $dom->createElement('alias'));
  357. if ($id) {
  358. $aliasXML->setAttribute('id', $id);
  359. }
  360. $aliasXML->setAttribute('service', (string) $alias);
  361. $aliasXML->setAttribute('public', $alias->isPublic() && !$alias->isPrivate() ? 'true' : 'false');
  362. return $dom;
  363. }
  364. private function getContainerParameterDocument($parameter, array $options = []): \DOMDocument
  365. {
  366. $dom = new \DOMDocument('1.0', 'UTF-8');
  367. $dom->appendChild($parameterXML = $dom->createElement('parameter'));
  368. if (isset($options['parameter'])) {
  369. $parameterXML->setAttribute('key', $options['parameter']);
  370. }
  371. $parameterXML->appendChild(new \DOMText($this->formatParameter($parameter)));
  372. return $dom;
  373. }
  374. private function getEventDispatcherListenersDocument(EventDispatcherInterface $eventDispatcher, string $event = null): \DOMDocument
  375. {
  376. $dom = new \DOMDocument('1.0', 'UTF-8');
  377. $dom->appendChild($eventDispatcherXML = $dom->createElement('event-dispatcher'));
  378. $registeredListeners = $eventDispatcher->getListeners($event);
  379. if (null !== $event) {
  380. $this->appendEventListenerDocument($eventDispatcher, $event, $eventDispatcherXML, $registeredListeners);
  381. } else {
  382. ksort($registeredListeners);
  383. foreach ($registeredListeners as $eventListened => $eventListeners) {
  384. $eventDispatcherXML->appendChild($eventXML = $dom->createElement('event'));
  385. $eventXML->setAttribute('name', $eventListened);
  386. $this->appendEventListenerDocument($eventDispatcher, $eventListened, $eventXML, $eventListeners);
  387. }
  388. }
  389. return $dom;
  390. }
  391. private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, string $event, \DOMElement $element, array $eventListeners)
  392. {
  393. foreach ($eventListeners as $listener) {
  394. $callableXML = $this->getCallableDocument($listener);
  395. $callableXML->childNodes->item(0)->setAttribute('priority', $eventDispatcher->getListenerPriority($event, $listener));
  396. $element->appendChild($element->ownerDocument->importNode($callableXML->childNodes->item(0), true));
  397. }
  398. }
  399. private function getCallableDocument($callable): \DOMDocument
  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. $r = new \ReflectionFunction($callable);
  437. if (false !== strpos($r->name, '{closure}')) {
  438. return $dom;
  439. }
  440. $callableXML->setAttribute('name', $r->name);
  441. if ($class = $r->getClosureScopeClass()) {
  442. $callableXML->setAttribute('class', $class->name);
  443. if (!$r->getClosureThis()) {
  444. $callableXML->setAttribute('static', 'true');
  445. }
  446. }
  447. return $dom;
  448. }
  449. if (method_exists($callable, '__invoke')) {
  450. $callableXML->setAttribute('type', 'object');
  451. $callableXML->setAttribute('name', \get_class($callable));
  452. return $dom;
  453. }
  454. throw new \InvalidArgumentException('Callable is not describable.');
  455. }
  456. }