Problem/Motivation
The performance pattern save_in_loop (modules/audit_performance/src/Plugin/AuditAnalyzer/PerformanceAnalyzer.php) matches any ->save() inside a loop and reports "Entity save() inside a loop - each save triggers hooks and cache invalidation", recommending batch operations or queue workers.
The pattern does not look at the receiver. A loop over configuration names that updates each config object, for example
foreach ($names as $name) {
$config = $this->configFactory->reset($name)->getEditable($name);
...
$config->save();
}
(the code core's LocaleConfigManager::updateDefaultConfigLangcodes() uses), or a config form saving several settings, is reported as an entity save. Config::save() does not run entity hooks or entity access, invalidates only the config: cache tag, and there is no bulk alternative: saving each config object is the normal way to update several of them.
Steps to reproduce
1. Add a loop calling $this->configFactory->getEditable($name)->set('langcode', $langcode)->save(); to a custom module.
2. Run drush audit:run performance --format=json.
3. SAVE_IN_LOOP is reported.
Proposed resolution
Add a skip_config_save flag to the save_in_loop pattern, handled in isContextualPerfIssue() by a new isConfigObjectSave() helper that skips the save() when it is made on a configuration object:
- a ->getEditable( or $this->config( call chained to the save(), also when the chain is split over the two previous lines (only a chain that ends on the reported line counts);
- or a variable or property whose closest assignment above is such a call ($config = ...->getEditable($name)).
The receiver is resolved from the code, not guessed from the variable name: a $config variable that is reassigned to an entity before the save() is still reported. Entity saves in loops are reported as before.
Remaining tasks
- Implement the flag and the helper.
- Unit test with a data provider (none covered SAVE_IN_LOOP before): config chained, chained over several lines, through a variable, and $this->config() in a config form => not reported; an entity save, a $config variable reassigned to an entity, an entity save after a config line, all in a loop => reported; an entity save outside a loop => not reported.
User interface changes
Configuration object saves inside loops no longer produce SAVE_IN_LOOP warnings.
API changes
None.
Data model changes
None.
Comments
Comment #3
trebormc