symfony 容器類緩存生成方式

2020-05-10 15:59:00
CJL
原創
3219

爲什麽symfony框架需要生成容器相關的緩存文件,要從symfony的依賴註入特性看起。


先來看一下java生態中的實現方式:


php是解釋型語言,無法在編譯器實現,所以php生態內的依賴註入基本都是通過容器管理及反射機製實現的。但每次通過反射分析類結構會影響性能,所以symfony框架在此基礎上將反射結果保存到php文件,這樣隻需要第一次訪問時生成緩存文件卽可通過緩存文件進行依賴註入。


那麽symfony緩存是何時生成的呢?我們看下symfony的生命週期:


在初始化容器階段(initializeContainer)會檢查緩存容器代碼是否有效,如果緩存已失效或不存在就會在此時生成容器併生成緩存


vendor/symfony/http-kernel/Kernel.php


    protected function initializeContainer()
    {
        $class = $this->getContainerClass(); //穫取容器classname
        $cacheDir = $this->warmupDir ?: $this->getCacheDir();
        $cache = new ConfigCache($cacheDir.'/'.$class.'.php', $this->debug);
        $oldContainer = null;
        if ($fresh = $cache->isFresh()) { //判斷容器緩存是否有效
            // Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
            $errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
            $fresh = $oldContainer = false;
            try {
                if (file_exists($cache->getPath()) && \is_object($this->container = include $cache->getPath())) { //緩存有效通過緩存文件穫取容器
                    $this->container->set('kernel', $this);
                    $oldContainer = $this->container;
                    $fresh = true;
                }
            } catch (\Throwable $e) {
            } finally {
                error_reporting($errorLevel);
            }
        }
        if ($fresh) {
            return;
        }
        
        //......
        //緩存失效
        try {
            $container = null;
            $container = $this->buildContainer(); //生成容器
            $container->compile(); //編譯,通過反射分析容器依賴
        } finally {
            //....
        }
        if (null === $oldContainer && file_exists($cache->getPath())) {
            $errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
            try {
                $oldContainer = include $cache->getPath();
            } catch (\Throwable $e) {
            } finally {
                error_reporting($errorLevel);
            }
        }
        $oldContainer = \is_object($oldContainer) ? new \ReflectionClass($oldContainer) : false;
        $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); //保存容器緩存
        $this->container = require $cache->getPath();
        $this->container->set('kernel', $this);
        if ($oldContainer && \get_class($this->container) !== $oldContainer->name) {
            // Because concurrent requests might still be using them,
            // old container files are not removed immediately,
            // but on a next dump of the container.
            static $legacyContainers = [];
            $oldContainerDir = \dirname($oldContainer->getFileName());
            $legacyContainers[$oldContainerDir.'.legacy'] = true;
            foreach (glob(\dirname($oldContainerDir).\DIRECTORY_SEPARATOR.'*.legacy', GLOB_NOSORT) as $legacyContainer) {
                if (!isset($legacyContainers[$legacyContainer]) && @unlink($legacyContainer)) {
                    (new Filesystem())->remove(substr($legacyContainer, 0, -7));
                }
            }
            touch($oldContainerDir.'.legacy');
        }
        if ($this->container->has('cache_warmer')) {
            $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir'));
        }
    }



通過容器初始化過程可以看到,關鍵流程在於容器緩存的有效性檢查與容器緩存的保存。


緩存有效性檢查時通過穫取衕名文件的meta信息(報錯容器緩存時一衕保存),將緩存依賴的相關信息進行檢查,比如 var/cache/dev/srcApp_KernelDevDebugContainer.php.meta內包含生成緩存時依賴的類、配置文件、vendor等資源信息,將保存的信息與當前信息進行對比,最簡單的是文件對比,meta信息中保存瞭文件生成時間,如果當前文件生成實際與緩存時間不一緻則文件失效。vendor/symfony/config/Resource 目録下包含瞭FileResource等資源的檢查實現


容器編譯過程

vendor/symfony/http-kernel/Kernel.php


protected function buildContainer()
    {
        //生成目録
        foreach (['cache' => $this->warmupDir ?: $this->getCacheDir(), 'logs' => $this->getLogDir()] as $name => $dir) {
            if (!is_dir($dir)) {
                if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
                    throw new \RuntimeException(sprintf("Unable to create the %s directory (%s)\n", $name, $dir));
                }
            } elseif (!is_writable($dir)) {
                throw new \RuntimeException(sprintf("Unable to write in the %s directory (%s)\n", $name, $dir));
            }
        }
        $container = $this->getContainerBuilder(); //穫取容器編譯容器
        $container->addObjectResource($this); 
        $this->prepareContainer($container); //準備容器所需資源、配置等
        if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) {
            $container->merge($cont);
        }
        $container->addCompilerPass(new AddAnnotatedClassesToCachePass($this)); //添加編譯器
        return $container;
    }


編譯過程中有重要的兩步:prepareContainer、compiler

vendor/symfony/http-kernel/Kernel.php


protected function prepareContainer(ContainerBuilder $container)
    {
        $extensions = [];
        foreach ($this->bundles as $bundle) {
            if ($extension = $bundle->getContainerExtension()) {
                $container->registerExtension($extension); //穫取擴展,一般在擴展代碼的DependencyInjection/{$name}Extension.php
            }
            if ($this->debug) {
                $container->addObjectResource($bundle);
            }
        }
        foreach ($this->bundles as $bundle) {
            $bundle->build($container); //編譯擴展 對應擴展代碼根目録的{$name}Bundle.php 對擴展需要的容器進行註冊
        }
        $this->build($container); // 無
        foreach ($container->getExtensions() as $extension) {
            $extensions[] = $extension->getAlias();
        }
        // ensure these extensions are implicitly loaded
        $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions));
    }
compiler 編譯



public function compile(bool $resolveEnvPlaceholders = false)
    {
        $compiler = $this->getCompiler();
        if ($this->trackResources) {
            foreach ($compiler->getPassConfig()->getPasses() as $pass) {
                $this->addObjectResource($pass);
            }
        }
        $bag = $this->getParameterBag();
        if ($resolveEnvPlaceholders && $bag instanceof EnvPlaceholderParameterBag) {
            $compiler->addPass(new ResolveEnvPlaceholdersPass(), PassConfig::TYPE_AFTER_REMOVING, -1000);
        }
        $compiler->compile($this); //編譯 內部循環上一步註冊的編譯器進行編譯
        foreach ($this->definitions as $id => $definition) {
            if ($this->trackResources && $definition->isLazy()) {
                $this->getReflectionClass($definition->getClass());
            }
        }
        $this->extensionConfigs = [];
        if ($bag instanceof EnvPlaceholderParameterBag) {
            if ($resolveEnvPlaceholders) {
                $this->parameterBag = new ParameterBag($this->resolveEnvPlaceholders($bag->all(), true));
            }
            $this->envPlaceholders = $bag->getEnvPlaceholders();
        }
        parent::compile();
        foreach ($this->definitions + $this->aliasDefinitions as $id => $definition) {
            if (!$definition->isPublic() || $definition->isPrivate()) {
                $this->removedIds[$id] = true;
            }
        }
    }
其中擴展的load方法便是在這一步通過vendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php 進行的調用


load方法內的processConfiguration會調用擴展代碼/src/DependencyInjection/Configuration.php內的getConfigTreeBuilder



編譯完成後會進行保存

vendor/symfony/http-kernel/Kernel.php :: dumpContainer 方法

實際通過vendor/symfony/dependency-injection/Dumper/PhpDumper.php進行文件的生成及保存



註解的解析依賴於

vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php



蔘考資料:

https://blog.csdn.net/u012129558/article/details/80901864 Java代理-動態字節碼生成代理的5種方式





發錶評論
評論通過審核後顯示。
流量統計