symfony源码分析之容器的生成与使用
symfony 的容器是有一个编译过程的,框架初始化的时候会执行Symfony\Component\HttpKernel\Kernel::initializationContainer ,这个方法会对代码进行检查,看是否需要生成新的容器代码。如果需要 Symfony 会将各个类的依赖关系通过编译生成静态的类并存储在缓存文件中var/cache/[ENV]/appProjectContainer。
其中很多是框架自己的依赖关系,这些依赖关系类似java的方式,通过xml文件的形式进行声明,这些xml存在与框架的代码中,Syfmony\Bundle\FrameworkBundle\Resource\config\*.xml。
0. 代码流程
容器相关的代码大致如下流程
# Symfony\Component\HttpKernel\Kernelprotected function initializeContainer(){ // 获取生成的容器代码文件 类名 $class = $this->getContainerClass(); $cache = new ConfigCache($this->getCacheDir().'/'.$class.'.php', $this->debug); $fresh = true; // 检查是否需要重新生成 容器 缓存类 if (!$cache->isFresh()) { // ..... try { $container = null; // 重新生成容器缓存类 // 实例化生成容器缓存代码的类 $container = $this->buildContainer(); // 调用方法开始生成容器代码 $container->compile(); } finally { // ...... } // 将生成的代码写入文件 $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); $fresh = false; } //加载生成的容器文件 实例化容器类 并且复制给 Kernel::$container require_once $cache->getPath(); $this->container = new $class(); $this->container->set('kernel', $this); // ......}
下面着重分析 容器代码生成类的创建过程
protected function buildContainer(){ // 创建容器缓存代码的目录 $container = $this->getContainerBuilder(); $container->addObjectResource($this); // 容器预处理,在开始编译容器前,会给容器添加一些 pass (这个问题就是在2017phpcon大会上提过的,当时没解决,这里看代码解决了) // 这些pass主要处理框架配置中声明的容器关系 $this->prepareContainer($container); // 解析容器配置文件 我们自己注入容器的类就要声明在配置文件 app/config/services.yml 中 // 这里就是对这个配置文件进行解析,并将其中的关系生成代码到容器中 if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { $container->merge($cont); } $container->addCompilerPass(new AddAnnotatedClassesToCachePass($this)); $container->addResource(new EnvParametersResource('SYMFONY__')); return $container;}
如上的分析可以将容器代码生成的过程精简到如下3个步骤
# Symfony\Component\HttpKernel\Kernelprotected function initializeContainer(){ // ... try { $container = null; $container = $this->buildContainer(); 3. 编译生成容器代码 $container->compile(); } finally { // ...... } // ......}protected function buildContainer(){ // 1. 实例化创建容器代码类的时候,预处理一些内容 $this->prepareContainer($container); // 2. 加载项目容器的配置文件进行处理 app/config/services.yml if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { $container->merge($cont); }}
1. 容器预处理
# Symfony\Component\HttpKernel\Kernelprotected function prepareContainer(ContainerBuilder $container){ $extensions = array(); // 这里的 bundles 是在 initializeBundles 方法中实例化AppKernel中注册的Bundles 进行的赋值 foreach ($this->bundles as $bundle) { // 这里将注册的Bundle进行循环处理 // 取出bundle的extension 并 注册到 container中 if ($extension = $bundle->getContainerExtension()) { $container->registerExtension($extension); $extensions[] = $extension->getAlias(); } if ($this->debug) { $container->addObjectResource($bundle); } } foreach ($this->bundles as $bundle) { // 触发bundle的build方法 $bundle->build($container); } $this->build($container); // 这里的代码是为了确定将 MergeExtensionConfigurationPass 这个Pass类添加到容器的Pass中 $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions));}
我们可以在 AppKernel::registerBundles 中查看到注册的 Bundles
public function registerBundles(){ $bundles = [ new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), new Symfony\Bundle\MonologBundle\MonologBundle(), new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(), new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new AppBundle\AppBundle(), ]; return $bundles;}
我们以 Symfony\Bundle\FrameworkBundle\FrameworkBundle 作为目标进行代码的继续跟踪
$extension = $bundle->getContainerExtension();$container->registerExtension($extension);
跟踪方法 getContainerExtension , 在类 FrameworkBundle 所继承的类Syfmony\Component\HttpKernel\Bundle\Bundle中找到
# Syfmony\Component\HttpKernel\Bundle\Bundlepublic function getContainerExtension(){ if (null === $this->extension) { // 获取extension $extension = $this->createContainerExtension(); if (null !== $extension) { // check naming convention $basename = preg_replace('/Bundle$/', '', $this->getName()); $expectedAlias = Container::underscore($basename); if ($expectedAlias != $extension->getAlias()) { throw new \LogicException(......); } $this->extension = $extension; } else { $this->extension = false; } } if ($this->extension) { return $this->extension; }}
以下是获取extension的代码逻辑
protected function createContainerExtension(){ // 这里实例化一个类并返回 if (class_exists($class = $this->getContainerExtensionClass())) { return new $class(); }}protected function getContainerExtensionClass(){ // 类名的规则 // 命名空间\DependencyInjection\(将类名的Bundle替换成Extension) $basename = preg_replace('/Bundle$/', '', $this->getName()); return $this->getNamespace().'\\DependencyInjection\\'.$basename.'Extension';}# 以下两个方法主要还是当 $this->namespace $this->name 不存在的时候# 通过方法 $this->parseClassName() 来解析出来public function getNamespace(){ if (null === $this->namespace) { $this->parseClassName(); } return $this->namespace;}final public function getName(){ if (null === $this->name) { $this->parseClassName(); } return $this->name;}private function parseClassName(){ # 命名空间 类型的截取 $pos = strrpos(static::class, '\\'); // 截取类的命名空间 $this->namespace = false === $pos ? '' : substr(static::class, 0, $pos); if (null === $this->name) { // 截取类的名称 $this->name = false === $pos ? static::class : substr(static::class, $pos + 1); }}
上代码解析出来的就是 Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension。回到代码中
if ($extension = $bundle->getContainerExtension()) { $container->registerExtension($extension); $extensions[] = $extension->getAlias();}
要执行容器的registerExtension方法
# Symfony\Component\DependencyInjection\ContainerBuilderpublic function registerExtension(ExtensionInterface $extension){ // $extension->getAlias() 这个方法在 Symfony\Component\DependencyInjection\Extension\Extension 中 // 主要的作用是将类名取出 将类名的Excention后缀去掉 $this->extensions[$extension->getAlias()] = $extension; if (false !== $extension->getNamespace()) { $this->extensionsByNs[$extension->getNamespace()] = $extension; }}
2. 解析容器配置文件
解析容器配置文件的代码
if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { $container->merge($cont);}
主要是执行了方法 registerContainerConfiguration 这个方法在 AppKernel 类中
# AppKernelpublic function registerContainerConfiguration(LoaderInterface $loader){ // 这里的配置文件是 app/config/config_[ENV].yml 是Yml格式的文件 // 这里的loader 应该是类 `YamlFileLoader` $loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');}# 所以这里的代码应该是执行 # SymfonyComponent\DependencyInjection\Loader\YamlFileLoader::loadpublic function load($resource, $type = null){ // 加载配置文件,将yml格式的配置内容解析成php数组 $content = $this->loadFile($path); $this->container->fileExists($path); // ...... // 处理配置文件中的 key `imports` 对import 声明的文件进行load处理 $this->parseImports($content, $path); // 处理配置文件中的参数部分 if (isset($content['parameters'])) { if (!is_array($content['parameters'])) { throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in %s. Check your YAML syntax.', $resource)); } foreach ($content['parameters'] as $key => $value) { $this->container->setParameter($key, $this->resolveServices($value, $resource, true)); } } // extensions $this->loadFromExtensions($content); // services $this->anonymousServicesCount = 0; $this->setCurrentDir(dirname($path)); try { // 这里将services中的声明封装到Definition 将Definition 存储到Container中 $this->parseDefinitions($content, $resource); } finally { $this->instanceof = array(); }}private function parseDefinitions(array $content, $file){ // 如果配置文件中不存在key services 这里直接返回 if (!isset($content['services'])) { return; } // ...... // 这里处理 配置文件中 services 下的 _default 配置信息,处理后会删除这个key $defaults = $this->parseDefaults($content, $file); //对配置文件中定义的services进行逐个分析 foreach ($content['services'] as $id => $service) { $this->parseDefinition($id, $service, $file, $defaults); }}private function parseDefinition($id, $service, $file, array $defaults){ //实例化 definition if ($this->isLoadingInstanceof) { $definition = new ChildDefinition(''); } elseif (isset($service['parent'])) { // ...... $definition = new ChildDefinition($service['parent']); } else { $definition = new Definition(); // ...... } // 这里的大段逻辑是处理一些services相关的配置信息 if (isset($service['class'])) { $definition->setClass($service['class']); } // ...... if (array_key_exists('resource', $service)) { if (!is_string($service['resource'])) { throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in %s. Check your YAML syntax.', $id, $file)); } $exclude = isset($service['exclude']) ? $service['exclude'] : null; // 如果service的配置中存在key resource 进行类的注册 $this->registerClasses($definition, $id, $service['resource'], $exclude); } else { $this->setDefinition($id, $definition); }}# Symfony\Component\DependencyInjection\Loader\FileLoaderpublic function registerClasses(Definition $prototype, $namespace, $resource, $exclude = null){ // 一些判断 // ...... // 这里主要是处理 存在 resource 这个key的services注册 // symfony的容器会将指定目录下都所有类都会注册到container // 这里的 findClasses 就是查询类的过程 $classes = $this->findClasses($namespace, $resource, $exclude); // prepare for deep cloning $prototype = serialize($prototype); foreach ($classes as $class) { // 这里调用 setDefinition 对resource下的类全部设置到 container中 $this->setDefinition($class, unserialize($prototype)); }}protected function setDefinition($id, Definition $definition){ // ...... //将definition添加到 container 里面 $this->container->setDefinition($id, $definition instanceof ChildDefinition ? $definition : $definition->setInstanceofConditionals($this->instanceof));}
这里着重分析下 registerClasses方法中的 $this->findClasses
# Symfony\Component\DependencyInjection\Loader\FileLoaderprivate function findClasses($namespace, $pattern, $excludePattern){ // ...... // 这里是对文件的处理 foreach ($this->glob($pattern, true, $resource) as $path => $info) { // ...... if (!$r->isInterface() && !$r->isTrait() && !$r->isAbstract()) { $classes[] = $class; } } // ...... return $classes;}# Symfony\Component\Config\Loader\FileLoaderprotected function glob($pattern, $recursive, &$resource = null, $ignoreErrors = false){ try { $prefix = $this->locator->locate($prefix, $this->currentDir, true); } catch (FileLocatorFileNotFoundException $e) { // ...... } // 这里返回的是 yield $resource = new GlobResource($prefix, $pattern, $recursive); // 所以这里需要foreach来触发 foreach ($resource as $path => $info) { yield $path => $info; }}# Symfony\Component\Config\Resource\GlobResourcepublic function getIterator(){ if (false === strpos($this->pattern, '/**/') && (defined('GLOB_BRACE') || false === strpos($this->pattern, '{'))) { foreach (glob($this->prefix.$this->pattern, defined('GLOB_BRACE') ? GLOB_BRACE : 0) as $path) { if ($this->recursive && is_dir($path)) { // 这里是读取文件的主要逻辑 // 使用的是 php 中提供的处理文件的迭代器 $files = iterator_to_array(new \RecursiveIteratorIterator( new \RecursiveCallbackFilterIterator( new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), function (\SplFileInfo $file) { return '.' !== $file->getBasename()[0]; } ), \RecursiveIteratorIterator::LEAVES_ONLY )); } elseif (is_file($path)) { yield $path => new \SplFileInfo($path); } // ...... } return; } $finder = new Finder(); // ...... foreach ($finder->followLinks()->sortByName()->in($this->prefix) as $path => $info) { if (preg_match($regex, substr($path, $prefixLen)) && $info->isFile()) { yield $path => $info; } }}
3. 编译生成容器代码
这段代码主要是执行 Pass代码 这里主要跟踪下 MergeExtensionConfigurationPass 这个Pass,他执行了 FrameworkExtension::load ,这个方法中加载了框架需要的各种类以及依赖关系
# Symfony\Component\DependencyInjection\Complier\Complier public function compile(ContainerBuilder $container){ try { // 这里触发了所有Passs的 process方法 foreach ($this->passConfig->getPasses() as $pass) { $pass->process($container); } } catch (\Exception $e) { // ...... }}# Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPasspublic function process(ContainerBuilder $container){ // ...... parent::process($container);}# Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPasspublic function process(ContainerBuilder $container){ foreach ($container->getExtensions() as $name => $extension) { // ...... // 执行了 extension 的 load 方法 $extension->load($config, $tmpContainer); // ...... } $container->addDefinitions($definitions); $container->addAliases($aliases);}# Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtensionpublic function load(array $configs, ContainerBuilder $container){ /* 这个方法中的代码很多,主要内容是加载了很多的 xml 配置文件 这些配置文件的作用是声明框架所需要的类之间的关系 */}