symfony 的容器是有一个编译过程的,框架初始化的时候会执行Symfony\Component\HttpKernel\Kernel::initializationContainer,这个方法会对代码进行检查,看是否需要生成新的容器代码。如果需要 Symfony 会将各个类的依赖关系通过编译生成静态的类并存储在缓存文件中var/cache/[ENV]/appProjectContainer。

其中很多是框架自己的依赖关系,这些依赖关系类似java的方式,通过xml文件的形式进行声明,这些xml存在与框架的代码中,Syfmony\Bundle\FrameworkBundle\Resource\config\*.xml。

0. 代码流程

容器相关的代码大致如下流程

#Symfony\Component\HttpKernel\KernelprotectedfunctioninitializeContainer(){//获取生成的容器代码文件类名$class=$this->getContainerClass();$cache=newConfigCache($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::$containerrequire_once$cache->getPath();$this->container=new$class();$this->container->set('kernel',$this);//......}


下面着重分析 容器代码生成类的创建过程

protectedfunctionbuildContainer(){//创建容器缓存代码的目录$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(newAddAnnotatedClassesToCachePass($this));$container->addResource(newEnvParametersResource('SYMFONY__'));return$container;}


如上的分析可以将容器代码生成的过程精简到如下3个步骤

#Symfony\Component\HttpKernel\KernelprotectedfunctioninitializeContainer(){//...try{$container=null;$container=$this->buildContainer();3.编译生成容器代码$container->compile();}finally{//......}//......}protectedfunctionbuildContainer(){//1.实例化创建容器代码类的时候,预处理一些内容$this->prepareContainer($container);//2.加载项目容器的配置文件进行处理app/config/services.ymlif(null!==$cont=$this->registerContainerConfiguration($this->getContainerLoader($container))){$container->merge($cont);}}1. 容器预处理

#Symfony\Component\HttpKernel\KernelprotectedfunctionprepareContainer(ContainerBuilder$container){$extensions=array();//这里的bundles是在initializeBundles方法中实例化AppKernel中注册的Bundles进行的赋值foreach($this->bundlesas$bundle){//这里将注册的Bundle进行循环处理//取出bundle的extension并注册到container中if($extension=$bundle->getContainerExtension()){$container->registerExtension($extension);$extensions[]=$extension->getAlias();}if($this->debug){$container->addObjectResource($bundle);}}foreach($this->bundlesas$bundle){//触发bundle的build方法$bundle->build($container);}$this->build($container);//这里的代码是为了确定将MergeExtensionConfigurationPass这个Pass类添加到容器的Pass中$container->getCompilerPassConfig()->setMergePass(newMergeExtensionConfigurationPass($extensions));}


我们可以在AppKernel::registerBundles中查看到注册的 Bundles

publicfunctionregisterBundles(){$bundles=[newSymfony\Bundle\FrameworkBundle\FrameworkBundle(),newSymfony\Bundle\SecurityBundle\SecurityBundle(),newSymfony\Bundle\TwigBundle\TwigBundle(),newSymfony\Bundle\MonologBundle\MonologBundle(),newSymfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),newDoctrine\Bundle\DoctrineBundle\DoctrineBundle(),newSensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),newAppBundle\AppBundle(),];return$bundles;}


我们以Symfony\Bundle\FrameworkBundle\FrameworkBundle作为目标进行代码的继续跟踪

$extension=$bundle->getContainerExtension();$container->registerExtension($extension);


跟踪方法getContainerExtension, 在类FrameworkBundle所继承的类Syfmony\Component\HttpKernel\Bundle\Bundle中找到

#Syfmony\Component\HttpKernel\Bundle\BundlepublicfunctiongetContainerExtension(){if(null===$this->extension){//获取extension$extension=$this->createContainerExtension();if(null!==$extension){//checknamingconvention$basename=preg_replace('/Bundle$/','',$this->getName());$expectedAlias=Container::underscore($basename);if($expectedAlias!=$extension->getAlias()){thrownew\LogicException(......);}$this->extension=$extension;}else{$this->extension=false;}}if($this->extension){return$this->extension;}}


以下是获取extension的代码逻辑

protectedfunctioncreateContainerExtension(){//这里实例化一个类并返回if(class_exists($class=$this->getContainerExtensionClass())){returnnew$class();}}protectedfunctiongetContainerExtensionClass(){//类名的规则//命名空间\DependencyInjection\(将类名的Bundle替换成Extension)$basename=preg_replace('/Bundle$/','',$this->getName());return$this->getNamespace().'\\DependencyInjection\\'.$basename.'Extension';}#以下两个方法主要还是当$this->namespace$this->name不存在的时候#通过方法$this->parseClassName()来解析出来publicfunctiongetNamespace(){if(null===$this->namespace){$this->parseClassName();}return$this->namespace;}finalpublicfunctiongetName(){if(null===$this->name){$this->parseClassName();}return$this->name;}privatefunctionparseClassName(){#命名空间类型的截取$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\ContainerBuilderpublicfunctionregisterExtension(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类中

#AppKernelpublicfunctionregisterContainerConfiguration(LoaderInterface$loader){//这里的配置文件是app/config/config_[ENV].yml是Yml格式的文件//这里的loader应该是类`YamlFileLoader`$loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');}#所以这里的代码应该是执行#SymfonyComponent\DependencyInjection\Loader\YamlFileLoader::loadpublicfunctionload($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'])){thrownewInvalidArgumentException(sprintf('The"parameters"keyshouldcontainanarrayin%s.CheckyourYAMLsyntax.',$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();}}privatefunctionparseDefinitions(array$content,$file){//如果配置文件中不存在keyservices这里直接返回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);}}privatefunctionparseDefinition($id,$service,$file,array$defaults){//实例化definitionif($this->isLoadingInstanceof){$definition=newChildDefinition('');}elseif(isset($service['parent'])){//......$definition=newChildDefinition($service['parent']);}else{$definition=newDefinition();//......}//这里的大段逻辑是处理一些services相关的配置信息if(isset($service['class'])){$definition->setClass($service['class']);}//......if(array_key_exists('resource',$service)){if(!is_string($service['resource'])){thrownewInvalidArgumentException(sprintf('A"resource"attributemustbeoftypestringforservice"%s"in%s.CheckyourYAMLsyntax.',$id,$file));}$exclude=isset($service['exclude'])?$service['exclude']:null;//如果service的配置中存在keyresource进行类的注册$this->registerClasses($definition,$id,$service['resource'],$exclude);}else{$this->setDefinition($id,$definition);}}#Symfony\Component\DependencyInjection\Loader\FileLoaderpublicfunctionregisterClasses(Definition$prototype,$namespace,$resource,$exclude=null){//一些判断//......//这里主要是处理存在resource这个key的services注册//symfony的容器会将指定目录下都所有类都会注册到container//这里的findClasses就是查询类的过程$classes=$this->findClasses($namespace,$resource,$exclude);//preparefordeepcloning$prototype=serialize($prototype);foreach($classesas$class){//这里调用setDefinition对resource下的类全部设置到container中$this->setDefinition($class,unserialize($prototype));}}protectedfunctionsetDefinition($id,Definition$definition){//......//将definition添加到container里面$this->container->setDefinition($id,$definitioninstanceofChildDefinition?$definition:$definition->setInstanceofConditionals($this->instanceof));}


这里着重分析下 registerClasses方法中的$this->findClasses

#Symfony\Component\DependencyInjection\Loader\FileLoaderprivatefunctionfindClasses($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\FileLoaderprotectedfunctionglob($pattern,$recursive,&$resource=null,$ignoreErrors=false){try{$prefix=$this->locator->locate($prefix,$this->currentDir,true);}catch(FileLocatorFileNotFoundException$e){//......}//这里返回的是yield$resource=newGlobResource($prefix,$pattern,$recursive);//所以这里需要foreach来触发foreach($resourceas$path=>$info){yield$path=>$info;}}#Symfony\Component\Config\Resource\GlobResourcepublicfunctiongetIterator(){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=newFinder();//......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\Complierpublicfunctioncompile(ContainerBuilder$container){try{//这里触发了所有Passs的process方法foreach($this->passConfig->getPasses()as$pass){$pass->process($container);}}catch(\Exception$e){//......}}#Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPasspublicfunctionprocess(ContainerBuilder$container){//......parent::process($container);}#Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPasspublicfunctionprocess(ContainerBuilder$container){foreach($container->getExtensions()as$name=>$extension){//......//执行了extension的load方法$extension->load($config,$tmpContainer);//......}$container->addDefinitions($definitions);$container->addAliases($aliases);}#Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtensionpublicfunctionload(array$configs,ContainerBuilder$container){/*这个方法中的代码很多,主要内容是加载了很多的xml配置文件这些配置文件的作用是声明框架所需要的类之间的关系*/}