Skip to content

Commit

Permalink
BUGFIX: neos#3303 Flush reflection cache for removed php classes
Browse files Browse the repository at this point in the history
There are a few layers to this bugfix.

1. Previously we ignored deleted files (see `file_exists`) and did not flush the `Flow_Reflection_Status` cache accordingly as we are not able to read the namespace from a deleted php file.
Now we do a best effort reverse lookup by going through all psr-4 autoload configurations and calculate a namespace under which composer would have found this file.

2. We introduce a state `removedReflectionDataClasses` like `updatedReflectionData` which will be used to check if we have to update the `ReflectionData` cache.
  • Loading branch information
mhsdesign committed Jul 27, 2024
1 parent fd3b1a8 commit 4175b39
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 8 deletions.
44 changes: 38 additions & 6 deletions Neos.Flow/Classes/Cache/CacheManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
use Neos\Cache\Frontend\VariableFrontend;
use Neos\Flow\Configuration\ConfigurationManager;
use Neos\Flow\Log\Utility\LogEnvironment;
use Neos\Flow\Monitor\ChangeDetectionStrategy\ChangeDetectionStrategyInterface;
use Neos\Flow\Package\PackageManager;
use Neos\Flow\Utility\Environment;
use Neos\Utility\Files;
use Neos\Flow\Utility\PhpAnalyzer;
Expand Down Expand Up @@ -47,6 +49,11 @@ class CacheManager
*/
protected $configurationManager;

/**
* @var PackageManager
*/
protected $packageManager;

/**
* @var LoggerInterface
*/
Expand Down Expand Up @@ -118,6 +125,11 @@ public function injectConfigurationManager(ConfigurationManager $configurationMa
$this->configurationManager = $configurationManager;
}

public function injectPackageManager(PackageManager $packageManager): void
{
$this->packageManager = $packageManager;
}

/**
* @param Environment $environment
* @return void
Expand Down Expand Up @@ -352,15 +364,35 @@ protected function flushClassCachesByChangedFiles(array $changedFiles): void
$modifiedAspectClassNamesWithUnderscores = [];
$modifiedClassNamesWithUnderscores = [];
foreach ($changedFiles as $pathAndFilename => $status) {
if (!file_exists($pathAndFilename)) {
continue;
$classNameWithUnderscores = null;
if ($status !== ChangeDetectionStrategyInterface::STATUS_DELETED) {
$fileContents = file_get_contents($pathAndFilename);
$className = (new PhpAnalyzer($fileContents))->extractFullyQualifiedClassName();
if ($className === null) {
continue;
}
$classNameWithUnderscores = str_replace('\\', '_', $className);
} else {
// file was deleted
foreach ($this->packageManager->getFlowPackages() as $flowPackage) {
if (!str_starts_with($pathAndFilename, $flowPackage->getPackagePath())) {
continue;
}
foreach ($flowPackage->getFlattenedAutoloadConfiguration() as $autoloadConfiguration) {
if (!str_starts_with($pathAndFilename, $autoloadConfiguration['classPath'])) {
continue;
}
$relativePath = substr($pathAndFilename, strlen($autoloadConfiguration['classPath']), -strlen('.php'));
$classNameWithUnderscores = str_replace('\\', '_', rtrim($autoloadConfiguration['namespace'], '\\')) . '_' . str_replace('/', '_', ltrim($relativePath, '/'));
break 2;
}
}
}
$fileContents = file_get_contents($pathAndFilename);
$className = (new PhpAnalyzer($fileContents))->extractFullyQualifiedClassName();
if ($className === null) {

if ($classNameWithUnderscores === null) {
continue;
}
$classNameWithUnderscores = str_replace('\\', '_', $className);

$modifiedClassNamesWithUnderscores[$classNameWithUnderscores] = true;

// If an aspect was modified, the whole code cache needs to be flushed, so keep track of them:
Expand Down
1 change: 1 addition & 0 deletions Neos.Flow/Classes/Core/Booting/Scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ public static function initializeCacheManagement(Bootstrap $bootstrap)
$cacheManager = new CacheManager();
$cacheManager->setCacheConfigurations($configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_CACHES));
$cacheManager->injectConfigurationManager($configurationManager);
$cacheManager->injectPackageManager($bootstrap->getEarlyInstance(PackageManager::class));
$cacheManager->injectLogger($bootstrap->getEarlyInstance(PsrLoggerFactoryInterface::class)->get('systemLogger'));
$cacheManager->injectEnvironment($environment);
$cacheManager->injectCacheFactory($cacheFactory);
Expand Down
11 changes: 9 additions & 2 deletions Neos.Flow/Classes/Reflection/ReflectionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ class ReflectionService
*/
protected array $updatedReflectionData = [];

/**
* Array with removed reflection information (e.g. in Development context after classes were deleted)
*/
protected array $removedReflectionDataClasses = [];

protected bool $initialized = false;

/**
Expand Down Expand Up @@ -1833,6 +1838,8 @@ protected function forgetClass($className): void

unset($this->classReflectionData[$className]);
unset($this->classesCurrentlyBeingForgotten[$className]);

$this->removedReflectionDataClasses[$className] = true;
}

/**
Expand Down Expand Up @@ -2024,7 +2031,7 @@ public function saveToCache(): void
$this->reflectionDataRuntimeCache->set('__availableClassNames', $this->availableClassNames);
}

if ($this->updatedReflectionData !== []) {
if ($this->updatedReflectionData !== [] || $this->removedReflectionDataClasses !== []) {
$this->updateReflectionData();
}

Expand Down Expand Up @@ -2092,7 +2099,7 @@ protected function saveProductionData(): void
*/
protected function updateReflectionData(): void
{
$this->log(sprintf('Found %s classes whose reflection data was not cached previously.', count($this->updatedReflectionData)), LogLevel::DEBUG);
$this->log(sprintf('Found %s classes whose reflection data was not cached previously and %s classes which were deleted.', count($this->updatedReflectionData), count($this->removedReflectionDataClasses)), LogLevel::DEBUG);

foreach (array_keys($this->updatedReflectionData) as $className) {
$this->statusCache->set($this->produceCacheIdentifierFromClassName($className), '');
Expand Down

0 comments on commit 4175b39

Please sign in to comment.