Chris@14: Chris@14: * Chris@14: * For the full copyright and license information, please view the LICENSE Chris@14: * file that was distributed with this source code. Chris@14: */ Chris@14: Chris@14: namespace Symfony\Component\HttpKernel\DependencyInjection; Chris@14: Chris@14: use Composer\Autoload\ClassLoader; Chris@14: use Symfony\Component\Debug\DebugClassLoader; Chris@17: use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; Chris@14: use Symfony\Component\DependencyInjection\ContainerBuilder; Chris@14: use Symfony\Component\HttpKernel\Kernel; Chris@14: Chris@14: /** Chris@14: * Sets the classes to compile in the cache for the container. Chris@14: * Chris@14: * @author Fabien Potencier Chris@14: */ Chris@14: class AddAnnotatedClassesToCachePass implements CompilerPassInterface Chris@14: { Chris@14: private $kernel; Chris@14: Chris@14: public function __construct(Kernel $kernel) Chris@14: { Chris@14: $this->kernel = $kernel; Chris@14: } Chris@14: Chris@14: /** Chris@14: * {@inheritdoc} Chris@14: */ Chris@14: public function process(ContainerBuilder $container) Chris@14: { Chris@17: $classes = []; Chris@17: $annotatedClasses = []; Chris@14: foreach ($container->getExtensions() as $extension) { Chris@14: if ($extension instanceof Extension) { Chris@14: if (\PHP_VERSION_ID < 70000) { Chris@14: $classes = array_merge($classes, $extension->getClassesToCompile()); Chris@14: } Chris@14: $annotatedClasses = array_merge($annotatedClasses, $extension->getAnnotatedClassesToCompile()); Chris@14: } Chris@14: } Chris@14: Chris@14: $existingClasses = $this->getClassesInComposerClassMaps(); Chris@14: Chris@14: if (\PHP_VERSION_ID < 70000) { Chris@14: $classes = $container->getParameterBag()->resolveValue($classes); Chris@14: $this->kernel->setClassCache($this->expandClasses($classes, $existingClasses)); Chris@14: } Chris@14: $annotatedClasses = $container->getParameterBag()->resolveValue($annotatedClasses); Chris@14: $this->kernel->setAnnotatedClassCache($this->expandClasses($annotatedClasses, $existingClasses)); Chris@14: } Chris@14: Chris@14: /** Chris@14: * Expands the given class patterns using a list of existing classes. Chris@14: * Chris@14: * @param array $patterns The class patterns to expand Chris@14: * @param array $classes The existing classes to match against the patterns Chris@14: * Chris@17: * @return array A list of classes derived from the patterns Chris@14: */ Chris@14: private function expandClasses(array $patterns, array $classes) Chris@14: { Chris@17: $expanded = []; Chris@14: Chris@14: // Explicit classes declared in the patterns are returned directly Chris@14: foreach ($patterns as $key => $pattern) { Chris@14: if ('\\' !== substr($pattern, -1) && false === strpos($pattern, '*')) { Chris@14: unset($patterns[$key]); Chris@14: $expanded[] = ltrim($pattern, '\\'); Chris@14: } Chris@14: } Chris@14: Chris@14: // Match patterns with the classes list Chris@14: $regexps = $this->patternsToRegexps($patterns); Chris@14: Chris@14: foreach ($classes as $class) { Chris@14: $class = ltrim($class, '\\'); Chris@14: Chris@14: if ($this->matchAnyRegexps($class, $regexps)) { Chris@14: $expanded[] = $class; Chris@14: } Chris@14: } Chris@14: Chris@14: return array_unique($expanded); Chris@14: } Chris@14: Chris@14: private function getClassesInComposerClassMaps() Chris@14: { Chris@17: $classes = []; Chris@14: Chris@14: foreach (spl_autoload_functions() as $function) { Chris@17: if (!\is_array($function)) { Chris@14: continue; Chris@14: } Chris@14: Chris@14: if ($function[0] instanceof DebugClassLoader) { Chris@14: $function = $function[0]->getClassLoader(); Chris@14: } Chris@14: Chris@17: if (\is_array($function) && $function[0] instanceof ClassLoader) { Chris@14: $classes += array_filter($function[0]->getClassMap()); Chris@14: } Chris@14: } Chris@14: Chris@14: return array_keys($classes); Chris@14: } Chris@14: Chris@14: private function patternsToRegexps($patterns) Chris@14: { Chris@17: $regexps = []; Chris@14: Chris@14: foreach ($patterns as $pattern) { Chris@14: // Escape user input Chris@14: $regex = preg_quote(ltrim($pattern, '\\')); Chris@14: Chris@14: // Wildcards * and ** Chris@17: $regex = strtr($regex, ['\\*\\*' => '.*?', '\\*' => '[^\\\\]*?']); Chris@14: Chris@14: // If this class does not end by a slash, anchor the end Chris@14: if ('\\' !== substr($regex, -1)) { Chris@14: $regex .= '$'; Chris@14: } Chris@14: Chris@14: $regexps[] = '{^\\\\'.$regex.'}'; Chris@14: } Chris@14: Chris@14: return $regexps; Chris@14: } Chris@14: Chris@14: private function matchAnyRegexps($class, $regexps) Chris@14: { Chris@14: $blacklisted = false !== strpos($class, 'Test'); Chris@14: Chris@14: foreach ($regexps as $regex) { Chris@14: if ($blacklisted && false === strpos($regex, 'Test')) { Chris@14: continue; Chris@14: } Chris@14: Chris@14: if (preg_match($regex, '\\'.$class)) { Chris@14: return true; Chris@14: } Chris@14: } Chris@14: Chris@14: return false; Chris@14: } Chris@14: }