FileLoader.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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\Component\DependencyInjection\Loader;
  11. use Symfony\Component\DependencyInjection\ChildDefinition;
  12. use Symfony\Component\DependencyInjection\ContainerBuilder;
  13. use Symfony\Component\DependencyInjection\Definition;
  14. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  15. use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader;
  16. use Symfony\Component\Config\FileLocatorInterface;
  17. use Symfony\Component\Config\Resource\GlobResource;
  18. /**
  19. * FileLoader is the abstract class used by all built-in loaders that are file based.
  20. *
  21. * @author Fabien Potencier <fabien@symfony.com>
  22. */
  23. abstract class FileLoader extends BaseFileLoader
  24. {
  25. protected $container;
  26. protected $isLoadingInstanceof = false;
  27. protected $instanceof = array();
  28. public function __construct(ContainerBuilder $container, FileLocatorInterface $locator)
  29. {
  30. $this->container = $container;
  31. parent::__construct($locator);
  32. }
  33. /**
  34. * Registers a set of classes as services using PSR-4 for discovery.
  35. *
  36. * @param Definition $prototype A definition to use as template
  37. * @param string $namespace The namespace prefix of classes in the scanned directory
  38. * @param string $resource The directory to look for classes, glob-patterns allowed
  39. * @param string $exclude A globed path of files to exclude
  40. */
  41. public function registerClasses(Definition $prototype, $namespace, $resource, $exclude = null)
  42. {
  43. if ('\\' !== substr($namespace, -1)) {
  44. throw new InvalidArgumentException(sprintf('Namespace prefix must end with a "\\": %s.', $namespace));
  45. }
  46. if (!preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++$/', $namespace)) {
  47. throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: %s.', $namespace));
  48. }
  49. $classes = $this->findClasses($namespace, $resource, $exclude);
  50. // prepare for deep cloning
  51. $prototype = serialize($prototype);
  52. foreach ($classes as $class) {
  53. $this->setDefinition($class, unserialize($prototype));
  54. }
  55. }
  56. /**
  57. * Registers a definition in the container with its instanceof-conditionals.
  58. *
  59. * @param string $id
  60. * @param Definition $definition
  61. */
  62. protected function setDefinition($id, Definition $definition)
  63. {
  64. if ($this->isLoadingInstanceof) {
  65. if (!$definition instanceof ChildDefinition) {
  66. throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.', $id, get_class($definition)));
  67. }
  68. $this->instanceof[$id] = $definition;
  69. } else {
  70. $this->container->setDefinition($id, $definition instanceof ChildDefinition ? $definition : $definition->setInstanceofConditionals($this->instanceof));
  71. }
  72. }
  73. private function findClasses($namespace, $pattern, $excludePattern)
  74. {
  75. $parameterBag = $this->container->getParameterBag();
  76. $excludePaths = array();
  77. $excludePrefix = null;
  78. if ($excludePattern) {
  79. $excludePattern = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePattern));
  80. foreach ($this->glob($excludePattern, true, $resource) as $path => $info) {
  81. if (null === $excludePrefix) {
  82. $excludePrefix = $resource->getPrefix();
  83. }
  84. // normalize Windows slashes
  85. $excludePaths[str_replace('\\', '/', $path)] = true;
  86. }
  87. }
  88. $pattern = $parameterBag->unescapeValue($parameterBag->resolveValue($pattern));
  89. $classes = array();
  90. $extRegexp = defined('HHVM_VERSION') ? '/\\.(?:php|hh)$/' : '/\\.php$/';
  91. $prefixLen = null;
  92. foreach ($this->glob($pattern, true, $resource) as $path => $info) {
  93. if (null === $prefixLen) {
  94. $prefixLen = strlen($resource->getPrefix());
  95. if ($excludePrefix && 0 !== strpos($excludePrefix, $resource->getPrefix())) {
  96. throw new InvalidArgumentException(sprintf('Invalid "exclude" pattern when importing classes for "%s": make sure your "exclude" pattern (%s) is a subset of the "resource" pattern (%s)', $namespace, $excludePattern, $pattern));
  97. }
  98. }
  99. if (isset($excludePaths[str_replace('\\', '/', $path)])) {
  100. continue;
  101. }
  102. if (!preg_match($extRegexp, $path, $m) || !$info->isReadable()) {
  103. continue;
  104. }
  105. $class = $namespace.ltrim(str_replace('/', '\\', substr($path, $prefixLen, -strlen($m[0]))), '\\');
  106. if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $class)) {
  107. continue;
  108. }
  109. // check to make sure the expected class exists
  110. if (!$r = $this->container->getReflectionClass($class)) {
  111. throw new InvalidArgumentException(sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.', $class, $path, $pattern));
  112. }
  113. if ($r->isInstantiable()) {
  114. $classes[] = $class;
  115. }
  116. }
  117. // track only for new & removed files
  118. if ($resource instanceof GlobResource) {
  119. $this->container->addResource($resource);
  120. } else {
  121. foreach ($resource as $path) {
  122. $this->container->fileExists($path, false);
  123. }
  124. }
  125. return $classes;
  126. }
  127. }