php框架 主动生成缓存,symfony 容器类缓存生成方式

本文详细介绍了Symfony框架中依赖注入的实现原理,以及容器缓存的生成与验证过程。在PHP解释型语言环境下,Symfony通过反射机制实现依赖注入,但为了提高性能,它将反射结果保存为缓存文件。在容器初始化阶段,框架会检查缓存文件的有效性,如若失效则重新生成。编译过程涉及容器构建、扩展加载和编译器的使用,最终将配置和类结构编译成PHP文件进行保存。这一机制提高了应用的运行效率。
摘要由CSDN通过智能技术生成

为什么symfony框架需要生成容器相关的缓存文件,要从symfony的依赖注入特性看起。

先来看一下java生态中的实现方式:

2cec1dd1e3a8cff4e88ef8e504e9ec44.png

07daf6c9d62739a42f07588a299a2d2a.png

php是解释型语言,无法在编译器实现,所以php生态内的依赖注入基本都是通过容器管理及反射机制实现的。但每次通过反射分析类结构会影响性能,所以symfony框架在此基础上将反射结果保存到php文件,这样只需要第一次访问时生成缓存文件即可通过缓存文件进行依赖注入。

那么symfony缓存是何时生成的呢?我们看下symfony的生命周期:

0248e76432afca24e0304067b8eda49c.png

在初始化容器阶段(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

参考资料:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值