FrameworkExtension.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  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\DependencyInjection;
  11. use Symfony\Component\Config\Loader\LoaderInterface;
  12. use Symfony\Component\DependencyInjection\ContainerBuilder;
  13. use Symfony\Component\DependencyInjection\Definition;
  14. use Symfony\Component\DependencyInjection\Parameter;
  15. use Symfony\Component\DependencyInjection\Reference;
  16. use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
  17. use Symfony\Component\Config\Resource\FileResource;
  18. use Symfony\Component\Finder\Finder;
  19. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  20. use Symfony\Component\Config\FileLocator;
  21. use Symfony\Component\Config\Definition\Processor;
  22. /**
  23. * FrameworkExtension.
  24. *
  25. * @author Fabien Potencier <fabien@symfony.com>
  26. * @author Jeremy Mikola <jmikola@gmail.com>
  27. */
  28. class FrameworkExtension extends Extension
  29. {
  30. /**
  31. * Responds to the app.config configuration parameter.
  32. *
  33. * @param array $configs
  34. * @param ContainerBuilder $container
  35. */
  36. public function load(array $configs, ContainerBuilder $container)
  37. {
  38. $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
  39. $loader->load('web.xml');
  40. $loader->load('services.xml');
  41. // A translator must always be registered (as support is included by
  42. // default in the Form component). If disabled, an identity translator
  43. // will be used and everything will still work as expected.
  44. $loader->load('translation.xml');
  45. if ($container->getParameter('kernel.debug')) {
  46. $loader->load('debug.xml');
  47. $container->setDefinition('event_dispatcher', $container->findDefinition('debug.event_dispatcher'));
  48. $container->setAlias('debug.event_dispatcher', 'event_dispatcher');
  49. }
  50. $processor = new Processor();
  51. $configuration = new Configuration($container->getParameter('kernel.debug'));
  52. $config = $processor->processConfiguration($configuration, $configs);
  53. $container->setParameter('kernel.cache_warmup', $config['cache_warmer']);
  54. if (isset($config['charset'])) {
  55. $container->setParameter('kernel.charset', $config['charset']);
  56. }
  57. $container->setParameter('kernel.secret', $config['secret']);
  58. $container->setParameter('exception_listener.controller', $config['exception_controller']);
  59. if (!empty($config['test'])) {
  60. $loader->load('test.xml');
  61. }
  62. if (isset($config['session'])) {
  63. $this->registerSessionConfiguration($config['session'], $container, $loader);
  64. }
  65. if ($hasForm = isset($config['form']) && !empty($config['form']['enabled'])) {
  66. $this->registerFormConfiguration($config, $container, $loader);
  67. $config['validation']['enabled'] = true;
  68. }
  69. if (!empty($config['validation']['enabled'])) {
  70. $this->registerValidationConfiguration($config['validation'], $container, $loader);
  71. }
  72. if (isset($config['esi'])) {
  73. $this->registerEsiConfiguration($config['esi'], $loader);
  74. }
  75. if (isset($config['profiler'])) {
  76. $this->registerProfilerConfiguration($config['profiler'], $container, $loader);
  77. }
  78. if (isset($config['router'])) {
  79. $this->registerRouterConfiguration($config['router'], $container, $loader);
  80. }
  81. if (isset($config['templating'])) {
  82. $this->registerTemplatingConfiguration($config['templating'], $config['ide'], $container, $loader);
  83. }
  84. if (isset($config['translator'])) {
  85. $this->registerTranslatorConfiguration($config['translator'], $container);
  86. }
  87. $this->registerAnnotationsConfiguration($config['annotations'], $container, $loader);
  88. $this->addClassesToCompile(array(
  89. 'Symfony\\Component\\HttpFoundation\\ParameterBag',
  90. 'Symfony\\Component\\HttpFoundation\\HeaderBag',
  91. 'Symfony\\Component\\HttpFoundation\\Request',
  92. 'Symfony\\Component\\HttpFoundation\\Response',
  93. 'Symfony\\Component\\HttpFoundation\\ResponseHeaderBag',
  94. 'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface',
  95. 'Symfony\\Component\\EventDispatcher\\EventDispatcher',
  96. 'Symfony\\Component\\EventDispatcher\\Event',
  97. 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface',
  98. 'Symfony\\Component\\HttpKernel\\HttpKernel',
  99. 'Symfony\\Component\\HttpKernel\\ResponseListener',
  100. 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver',
  101. 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface',
  102. 'Symfony\\Component\\HttpKernel\\Event\\KernelEvent',
  103. 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent',
  104. 'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent',
  105. 'Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent',
  106. 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent',
  107. 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent',
  108. 'Symfony\\Component\\HttpKernel\\Events',
  109. 'Symfony\\Bundle\\FrameworkBundle\\Listener\\RequestAttributeInitializingListener',
  110. 'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerNameParser',
  111. 'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver',
  112. 'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller',
  113. 'Symfony\\Bundle\\FrameworkBundle\\ContainerAwareEventDispatcher',
  114. ));
  115. }
  116. /**
  117. * Loads Form configuration.
  118. *
  119. * @param array $config A configuration array
  120. * @param ContainerBuilder $container A ContainerBuilder instance
  121. * @param XmlFileLoader $loader An XmlFileLoader instance
  122. */
  123. private function registerFormConfiguration($config, ContainerBuilder $container, XmlFileLoader $loader)
  124. {
  125. $loader->load('form.xml');
  126. if (isset($config['csrf_protection'])) {
  127. if (!isset($config['session'])) {
  128. throw new \LogicException('CSRF protection needs that sessions are enabled.');
  129. }
  130. $loader->load('form_csrf.xml');
  131. $container->setParameter('form.type_extension.csrf.enabled', $config['csrf_protection']['enabled']);
  132. $container->setParameter('form.type_extension.csrf.field_name', $config['csrf_protection']['field_name']);
  133. }
  134. if ($container->hasDefinition('session')) {
  135. $container->removeDefinition('file.temporary_storage');
  136. $container->setDefinition('file.temporary_storage', $container->getDefinition('file.temporary_storage.session'));
  137. $container->removeDefinition('file.temporary_storage.session');
  138. } else {
  139. $container->removeDefinition('file.temporary_storage.session');
  140. }
  141. }
  142. /**
  143. * Loads the ESI configuration.
  144. *
  145. * @param array $config An ESI configuration array
  146. * @param XmlFileLoader $loader An XmlFileLoader instance
  147. */
  148. private function registerEsiConfiguration(array $config, XmlFileLoader $loader)
  149. {
  150. if (!empty($config['enabled'])) {
  151. $loader->load('esi.xml');
  152. }
  153. }
  154. /**
  155. * Loads the profiler configuration.
  156. *
  157. * @param array $config A profiler configuration array
  158. * @param ContainerBuilder $container A ContainerBuilder instance
  159. * @param XmlFileLoader $loader An XmlFileLoader instance
  160. */
  161. private function registerProfilerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
  162. {
  163. $loader->load('profiling.xml');
  164. $loader->load('collectors.xml');
  165. $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']);
  166. $container->setParameter('profiler_listener.only_master_requests', $config['only_master_requests']);
  167. // Choose storage class based on the DSN
  168. $supported = array(
  169. 'sqlite' => 'Symfony\Component\HttpKernel\Profiler\SqliteProfilerStorage',
  170. 'mysql' => 'Symfony\Component\HttpKernel\Profiler\MysqlProfilerStorage',
  171. );
  172. list($class, ) = explode(':', $config['dsn']);
  173. if (!isset($supported[$class])) {
  174. throw new \LogicException(sprintf('Driver "%s" is not supported for the profiler.', $class));
  175. }
  176. $container->setParameter('profiler.storage.dsn', $config['dsn']);
  177. $container->setParameter('profiler.storage.username', $config['username']);
  178. $container->setParameter('profiler.storage.password', $config['password']);
  179. $container->setParameter('profiler.storage.lifetime', $config['lifetime']);
  180. $container->getDefinition('profiler.storage')->setClass($supported[$class]);
  181. if (isset($config['matcher'])) {
  182. if (isset($config['matcher']['service'])) {
  183. $container->setAlias('profiler.request_matcher', $config['matcher']['service']);
  184. } elseif (isset($config['matcher']['ip']) || isset($config['matcher']['path'])) {
  185. $definition = $container->register('profiler.request_matcher', 'Symfony\\Component\\HttpFoundation\\RequestMatcher');
  186. $definition->setPublic(false);
  187. if (isset($config['matcher']['ip'])) {
  188. $definition->addMethodCall('matchIp', array($config['matcher']['ip']));
  189. }
  190. if (isset($config['matcher']['path'])) {
  191. $definition->addMethodCall('matchPath', array($config['matcher']['path']));
  192. }
  193. }
  194. }
  195. }
  196. /**
  197. * Loads the router configuration.
  198. *
  199. * @param array $config A router configuration array
  200. * @param ContainerBuilder $container A ContainerBuilder instance
  201. * @param XmlFileLoader $loader An XmlFileLoader instance
  202. */
  203. private function registerRouterConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
  204. {
  205. $loader->load('routing.xml');
  206. $container->setParameter('router.resource', $config['resource']);
  207. $router = $container->findDefinition('router.real');
  208. if (isset($config['type'])) {
  209. $argument = $router->getArgument(2);
  210. $argument['resource_type'] = $config['type'];
  211. $router->replaceArgument(2, $argument);
  212. }
  213. if ($config['cache_warmer']) {
  214. $container->getDefinition('router.cache_warmer')->addTag('kernel.cache_warmer');
  215. $container->setAlias('router', 'router.cached');
  216. }
  217. $container->setParameter('request_listener.http_port', $config['http_port']);
  218. $container->setParameter('request_listener.https_port', $config['https_port']);
  219. $this->addClassesToCompile(array(
  220. 'Symfony\\Component\\Routing\\RouterInterface',
  221. 'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface',
  222. 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
  223. 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface',
  224. 'Symfony\\Component\\Routing\\Generator\\UrlGenerator',
  225. $container->findDefinition('router')->getClass(),
  226. ));
  227. }
  228. /**
  229. * Loads the session configuration.
  230. *
  231. * @param array $config A session configuration array
  232. * @param ContainerBuilder $container A ContainerBuilder instance
  233. * @param XmlFileLoader $loader An XmlFileLoader instance
  234. */
  235. private function registerSessionConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
  236. {
  237. $loader->load('session.xml');
  238. // session
  239. $session = $container->getDefinition('session');
  240. if (!empty($config['auto_start'])) {
  241. $session->addMethodCall('start');
  242. }
  243. $container->setParameter('session.default_locale', $config['default_locale']);
  244. // session storage
  245. $container->setAlias('session.storage', $config['storage_id']);
  246. $options = array();
  247. foreach (array('name', 'lifetime', 'path', 'domain', 'secure', 'httponly') as $key) {
  248. if (isset($config[$key])) {
  249. $options[$key] = $config[$key];
  250. }
  251. }
  252. $container->setParameter('session.storage.options', $options);
  253. $this->addClassesToCompile(array(
  254. 'Symfony\\Bundle\\FrameworkBundle\\Listener\\SessionInitializingListener',
  255. 'Symfony\\Component\\HttpFoundation\\SessionStorage\\SessionStorageInterface',
  256. $container->getDefinition('session')->getClass(),
  257. ));
  258. if ($container->hasDefinition($config['storage_id'])) {
  259. $this->addClassesToCompile(array(
  260. $container->findDefinition('session.storage')->getClass(),
  261. ));
  262. }
  263. }
  264. /**
  265. * Loads the templating configuration.
  266. *
  267. * @param array $config A templating configuration array
  268. * @param string $ide
  269. * @param ContainerBuilder $container A ContainerBuilder instance
  270. * @param XmlFileLoader $loader An XmlFileLoader instance
  271. */
  272. private function registerTemplatingConfiguration(array $config, $ide, ContainerBuilder $container, XmlFileLoader $loader)
  273. {
  274. $loader->load('templating.xml');
  275. $loader->load('templating_php.xml');
  276. $links = array(
  277. 'textmate' => 'txmt://open?url=file://%f&line=%l',
  278. 'macvim' => 'mvim://open?url=file://%f&line=%l',
  279. );
  280. $container->setParameter('templating.helper.code.file_link_format', str_replace('%', '%%', isset($links[$ide]) ? $links[$ide] : $ide));
  281. if ($container->getParameter('kernel.debug')) {
  282. $loader->load('templating_debug.xml');
  283. }
  284. $packages = array();
  285. foreach ($config['packages'] as $name => $package) {
  286. $packages[$name] = new Definition('%templating.asset_package.class%', array(
  287. $package['base_urls'],
  288. $package['version'],
  289. ));
  290. }
  291. $container->setParameter('templating.helper.assets.assets_base_urls', isset($config['assets_base_urls']) ? $config['assets_base_urls'] : array());
  292. $container->setParameter('templating.helper.assets.assets_version', $config['assets_version']);
  293. $container->getDefinition('templating.helper.assets')->replaceArgument(3, $packages);
  294. if (!empty($config['loaders'])) {
  295. $loaders = array_map(function($loader) { return new Reference($loader); }, $config['loaders']);
  296. // Use a delegation unless only a single loader was registered
  297. if (1 === count($loaders)) {
  298. $container->setAlias('templating.loader', (string) reset($loaders));
  299. } else {
  300. $container->getDefinition('templating.loader.chain')->addArgument($loaders);
  301. $container->setAlias('templating.loader', 'templating.loader.chain');
  302. }
  303. }
  304. $container->setParameter('templating.loader.cache.path', null);
  305. if (isset($config['cache'])) {
  306. // Wrap the existing loader with cache (must happen after loaders are registered)
  307. $container->setDefinition('templating.loader.wrapped', $container->findDefinition('templating.loader'));
  308. $loaderCache = $container->getDefinition('templating.loader.cache');
  309. $container->setParameter('templating.loader.cache.path', $config['cache']);
  310. $container->setDefinition('templating.loader', $loaderCache);
  311. }
  312. if ($config['cache_warmer']) {
  313. $container
  314. ->getDefinition('templating.cache_warmer.template_paths')
  315. ->addTag('kernel.cache_warmer', array('priority' => 20))
  316. ;
  317. $container->setAlias('templating.locator', 'templating.locator.cached');
  318. } else {
  319. $container->setAlias('templating.locator', 'templating.locator.uncached');
  320. }
  321. $this->addClassesToCompile(array(
  322. 'Symfony\\Bundle\\FrameworkBundle\\Templating\\EngineInterface',
  323. 'Symfony\\Component\\Templating\\TemplateNameParserInterface',
  324. 'Symfony\\Component\\Templating\\TemplateNameParser',
  325. 'Symfony\\Component\\Templating\\EngineInterface',
  326. 'Symfony\\Component\\Config\\FileLocatorInterface',
  327. 'Symfony\\Component\\Templating\\TemplateReferenceInterface',
  328. 'Symfony\\Component\\Templating\\TemplateReference',
  329. 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateReference',
  330. 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateNameParser',
  331. $container->findDefinition('templating.locator')->getClass(),
  332. ));
  333. if (in_array('php', $config['engines'], true)) {
  334. $this->addClassesToCompile(array(
  335. 'Symfony\\Component\\Templating\\PhpEngine',
  336. 'Symfony\\Component\\Templating\\Loader\\LoaderInterface',
  337. 'Symfony\\Component\\Templating\\Storage\\Storage',
  338. 'Symfony\\Component\\Templating\\Storage\\FileStorage',
  339. 'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine',
  340. 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader',
  341. ));
  342. }
  343. $container->setParameter('templating.engines', $config['engines']);
  344. $engines = array_map(function($engine) { return new Reference('templating.engine.'.$engine); }, $config['engines']);
  345. // Use a delegation unless only a single engine was registered
  346. if (1 === count($engines)) {
  347. $container->setAlias('templating', (string) reset($engines));
  348. } else {
  349. $container->getDefinition('templating.engine.delegating')->replaceArgument(1, $engines);
  350. $container->setAlias('templating', 'templating.engine.delegating');
  351. }
  352. }
  353. /**
  354. * Loads the translator configuration.
  355. *
  356. * @param array $config A translator configuration array
  357. * @param ContainerBuilder $container A ContainerBuilder instance
  358. */
  359. private function registerTranslatorConfiguration(array $config, ContainerBuilder $container)
  360. {
  361. if (!empty($config['enabled'])) {
  362. // Use the "real" translator instead of the identity default
  363. $container->setDefinition('translator', $translator = $container->findDefinition('translator.real'));
  364. $translator->addMethodCall('setFallbackLocale', array($config['fallback']));
  365. // Discover translation directories
  366. $dirs = array();
  367. foreach ($container->getParameter('kernel.bundles') as $bundle) {
  368. $reflection = new \ReflectionClass($bundle);
  369. if (is_dir($dir = dirname($reflection->getFilename()).'/Resources/translations')) {
  370. $dirs[] = $dir;
  371. }
  372. }
  373. if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/translations')) {
  374. $dirs[] = $dir;
  375. }
  376. // Register translation resources
  377. if ($dirs) {
  378. $finder = new Finder();
  379. $finder->files()->filter(function (\SplFileInfo $file) { return 2 === substr_count($file->getBasename(), '.'); })->in($dirs);
  380. foreach ($finder as $file) {
  381. // filename is domain.locale.format
  382. list($domain, $locale, $format) = explode('.', $file->getBasename());
  383. $translator->addMethodCall('addResource', array($format, (string) $file, $locale, $domain));
  384. }
  385. }
  386. }
  387. }
  388. /**
  389. * Loads the validator configuration.
  390. *
  391. * @param array $config A validation configuration array
  392. * @param ContainerBuilder $container A ContainerBuilder instance
  393. * @param XmlFileLoader $loader An XmlFileLoader instance
  394. */
  395. private function registerValidationConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
  396. {
  397. $loader->load('validator.xml');
  398. $container->setParameter('validator.mapping.loader.xml_files_loader.mapping_files', $this->getValidatorXmlMappingFiles($container));
  399. $container->setParameter('validator.mapping.loader.yaml_files_loader.mapping_files', $this->getValidatorYamlMappingFiles($container));
  400. if ($config['enable_annotations']) {
  401. $loaderChain = $container->getDefinition('validator.mapping.loader.loader_chain');
  402. $arguments = $loaderChain->getArguments();
  403. array_unshift($arguments[0], new Reference('validator.mapping.loader.annotation_loader'));
  404. $loaderChain->setArguments($arguments);
  405. }
  406. if (isset($config['cache'])) {
  407. $container->getDefinition('validator.mapping.class_metadata_factory')
  408. ->replaceArgument(1, new Reference('validator.mapping.cache.'.$config['cache']));
  409. $container->setParameter(
  410. 'validator.mapping.cache.prefix',
  411. 'validator_'.md5($container->getParameter('kernel.root_dir'))
  412. );
  413. }
  414. }
  415. private function getValidatorXmlMappingFiles(ContainerBuilder $container)
  416. {
  417. $files = array(__DIR__.'/../../../Component/Form/Resources/config/validation.xml');
  418. $container->addResource(new FileResource($files[0]));
  419. foreach ($container->getParameter('kernel.bundles') as $bundle) {
  420. $reflection = new \ReflectionClass($bundle);
  421. if (file_exists($file = dirname($reflection->getFilename()).'/Resources/config/validation.xml')) {
  422. $files[] = realpath($file);
  423. $container->addResource(new FileResource($file));
  424. }
  425. }
  426. return $files;
  427. }
  428. private function getValidatorYamlMappingFiles(ContainerBuilder $container)
  429. {
  430. $files = array();
  431. foreach ($container->getParameter('kernel.bundles') as $bundle) {
  432. $reflection = new \ReflectionClass($bundle);
  433. if (file_exists($file = dirname($reflection->getFilename()).'/Resources/config/validation.yml')) {
  434. $files[] = realpath($file);
  435. $container->addResource(new FileResource($file));
  436. }
  437. }
  438. return $files;
  439. }
  440. private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container,$loader)
  441. {
  442. $loader->load('annotations.xml');
  443. if ('file' === $config['cache']) {
  444. $cacheDir = $container->getParameterBag()->resolveValue($config['file_cache_dir']);
  445. if (!is_dir($cacheDir) && false === @mkdir($cacheDir, 0777, true)) {
  446. throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $cacheDir));
  447. }
  448. $container
  449. ->getDefinition('annotations.file_cache_reader')
  450. ->replaceArgument(1, $cacheDir)
  451. ->replaceArgument(2, $config['debug'])
  452. ;
  453. $container->setAlias('annotation_reader', 'annotations.file_cache_reader');
  454. } else if('none' !== $config['cache']) {
  455. $container
  456. ->getDefinition('annotations.cached_reader')
  457. ->replaceArgument(1, new Reference($config['cache']))
  458. ->replaceArgument(2, $config['debug'])
  459. ;
  460. $container->setAlias('annotation_reader', 'annotations.cached_reader');
  461. }
  462. }
  463. /**
  464. * Returns the base path for the XSD files.
  465. *
  466. * @return string The XSD base path
  467. */
  468. public function getXsdValidationBasePath()
  469. {
  470. return __DIR__.'/../Resources/config/schema';
  471. }
  472. public function getNamespace()
  473. {
  474. return 'http://symfony.com/schema/dic/symfony';
  475. }
  476. }