FrameworkExtension.php 23 KB

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