Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUGFIX: #3303 Flush reflection cache for removed php classes #3383

Open
wants to merge 2 commits into
base: 9.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
*/
protected $configurationManager;

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

/**
* @var LoggerInterface
*/
Expand Down Expand Up @@ -118,6 +125,11 @@
$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 @@
$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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically we can discuss if we always want to use the psr-4 reverse lookup instead of parsing the file via PhpAnalyzer ... but for that we would need to know how stable this logic is... for example regarding symlinks

// file was deleted
foreach ($this->packageManager->getFlowPackages() as $flowPackage) {
if (!str_starts_with($pathAndFilename, $flowPackage->getPackagePath())) {
continue;
}
foreach ($flowPackage->getFlattenedAutoloadConfiguration() as $autoloadConfiguration) {

Check failure on line 381 in Neos.Flow/Classes/Cache/CacheManager.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test static analysis (deps: highest)

Call to an undefined method Neos\Flow\Package\FlowPackageInterface::getFlattenedAutoloadConfiguration().
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
20 changes: 13 additions & 7 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 @@ -1795,18 +1800,17 @@ protected function forgetClass($className): void
$this->classesCurrentlyBeingForgotten[$className] = true;

if (class_exists($className)) {
// optimisation to flush only precisely the affected interfaces
$interfaceNames = class_implements($className);
foreach ($interfaceNames as $interfaceName) {
if (isset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS][$className])) {
unset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS][$className]);
}
}
} else {
foreach ($this->availableClassNames as $interfaceNames) {
foreach ($interfaceNames as $interfaceName) {
if (isset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS][$className])) {
unset($this->classReflectionData[$interfaceName][self::DATA_INTERFACE_IMPLEMENTATIONS][$className]);
}
foreach ($this->classReflectionData as &$possibleInterfaceReflectionData) {
if (isset($possibleInterfaceReflectionData[self::DATA_INTERFACE_IMPLEMENTATIONS][$className])) {
unset($possibleInterfaceReflectionData[self::DATA_INTERFACE_IMPLEMENTATIONS][$className]);
}
}
}
Expand All @@ -1833,6 +1837,8 @@ protected function forgetClass($className): void

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

$this->removedReflectionDataClasses[$className] = true;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo check if forgetClass is also called in other occasions and if this flag makes sense in that case.

}

/**
Expand Down Expand Up @@ -2024,7 +2030,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 +2098,7 @@ protected function saveProductionData(): void
*/
protected function updateReflectionData(): void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$this->removedReflectionDataClasses should be reset to [] in here, right?

{
$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
Loading