DelegatingLoader.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  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\Routing;
  11. use Psr\Log\LoggerInterface;
  12. use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser;
  13. use Symfony\Component\Config\Exception\FileLoaderLoadException;
  14. use Symfony\Component\Config\Loader\DelegatingLoader as BaseDelegatingLoader;
  15. use Symfony\Component\Config\Loader\LoaderResolverInterface;
  16. /**
  17. * DelegatingLoader delegates route loading to other loaders using a loader resolver.
  18. *
  19. * This implementation resolves the _controller attribute from the short notation
  20. * to the fully-qualified form (from a:b:c to class::method).
  21. *
  22. * @author Fabien Potencier <fabien@symfony.com>
  23. */
  24. class DelegatingLoader extends BaseDelegatingLoader
  25. {
  26. protected $parser;
  27. protected $logger;
  28. private $loading = false;
  29. /**
  30. * Ability to pass a LoggerInterface instance as second argument will be removed in 3.0.
  31. *
  32. * @param ControllerNameParser $parser A ControllerNameParser instance
  33. * @param LoaderResolverInterface $resolver A LoaderResolverInterface instance
  34. */
  35. public function __construct(ControllerNameParser $parser, $resolver, $r = null)
  36. {
  37. $this->parser = $parser;
  38. if (!$resolver instanceof LoaderResolverInterface) {
  39. $this->logger = $resolver;
  40. $resolver = $r;
  41. @trigger_error('Passing a LoggerInterface instance as the second argument of the '.__METHOD__.' method is deprecated since Symfony 2.8 and will not be supported anymore in 3.0.', E_USER_DEPRECATED);
  42. }
  43. parent::__construct($resolver);
  44. }
  45. /**
  46. * {@inheritdoc}
  47. */
  48. public function load($resource, $type = null)
  49. {
  50. if ($this->loading) {
  51. // This can happen if a fatal error occurs in parent::load().
  52. // Here is the scenario:
  53. // - while routes are being loaded by parent::load() below, a fatal error
  54. // occurs (e.g. parse error in a controller while loading annotations);
  55. // - PHP abruptly empties the stack trace, bypassing all catch blocks;
  56. // it then calls the registered shutdown functions;
  57. // - the ErrorHandler catches the fatal error and re-injects it for rendering
  58. // thanks to HttpKernel->terminateWithException() (that calls handleException());
  59. // - at this stage, if we try to load the routes again, we must prevent
  60. // the fatal error from occurring a second time,
  61. // otherwise the PHP process would be killed immediately;
  62. // - while rendering the exception page, the router can be required
  63. // (by e.g. the web profiler that needs to generate an URL);
  64. // - this handles the case and prevents the second fatal error
  65. // by triggering an exception beforehand.
  66. throw new FileLoaderLoadException($resource);
  67. }
  68. $this->loading = true;
  69. try {
  70. $collection = parent::load($resource, $type);
  71. } catch (\Exception $e) {
  72. $this->loading = false;
  73. throw $e;
  74. } catch (\Throwable $e) {
  75. $this->loading = false;
  76. throw $e;
  77. }
  78. $this->loading = false;
  79. foreach ($collection->all() as $route) {
  80. if (!\is_string($controller = $route->getDefault('_controller')) || !$controller) {
  81. continue;
  82. }
  83. try {
  84. $controller = $this->parser->parse($controller);
  85. } catch (\InvalidArgumentException $e) {
  86. // unable to optimize unknown notation
  87. }
  88. $route->setDefault('_controller', $controller);
  89. }
  90. return $collection;
  91. }
  92. }