#41273·magento2

`cache:disable <type>` promotes every `cache_types` value from `config.php` into `env.php`, and `cache:enable` does not undo it

Author: lbajsarowiczCreated Sep 17, 2026Updated Sep 17, 2026
LabelsIssue: ready for confirmationReported on 2.4.x

Preconditions and environment

  • Magento 2.4-develop at commit 12d212031006ec455fee51c1653a6ac230aeca54, installed with a plain setup:install, no third-party modules
  • PHP 8.4.25, MariaDB 11.4
  • 15 cache types are declared by the enabled modules on this installation, and setup:install writes all 15 into app/etc/env.php
  • app/etc/config.php is the shared, version-controlled deployment configuration file (the one app:config:dump writes to and the one that is meant to be committed), app/etc/env.php is the per-environment file that is not. Magento\Framework\App\DeploymentConfig\Reader::load() merges the two with array_replace_recursive() in the order config.php, then env.php, so a cache_types map placed in config.php is read and honoured normally: cache:status reports it correctly and Magento\Framework\App\Cache\State::isEnabled() returns the right value for every type.

Steps to reproduce

  1. Install Magento and confirm app/etc/env.php contains a cache_types section and app/etc/config.php does not.
  2. Move the whole cache_types map from app/etc/env.php into app/etc/config.php, so that the map exists only in the shared file:
php -r '
    $env = include "app/etc/env.php";
    $cfg = include "app/etc/config.php";
    $ct  = $env["cache_types"];
    unset($env["cache_types"]);
    $cfg["cache_types"] = $ct;
    file_put_contents("app/etc/env.php", "<?php\nreturn " . var_export($env, true) . ";\n");
    file_put_contents("app/etc/config.php", "<?php\nreturn " . var_export($cfg, true) . ";\n");
'
  1. Run bin/magento cache:status and confirm all 15 types are reported with their values from config.php.
  2. Run bin/magento cache:disable full_page.
  3. Inspect the cache_types section of both files.
  4. Run bin/magento cache:enable full_page and inspect both files again.

Expected result

Step 4 changes the state of one cache type. app/etc/env.php either stays without a cache_types section and the change is refused with an explanation, or it gains exactly the one entry that changed:

'cache_types' => [
    'full_page' => 0,
],

The remaining 14 types keep being resolved from config.php, so a later edit to config.php still takes effect, and step 6 returns the installation to the state it was in before step 4.

Actual result

Step 4 writes the whole merged 15-entry map into app/etc/env.php:

CMD: bin/magento cache:disable full_page
EXIT: 0
Changed cache status:
                     full_page: 1 -> 0
DIFF env.php cache_types (before -> after):
  REMOVED <NO_CACHE_TYPES_SECTION>
  ADDED   block_html: 1
  ADDED   collections: 1
  ADDED   compiled_config: 1
  ADDED   config: 1
  ADDED   config_integration: 1
  ADDED   config_integration_api: 1
  ADDED   config_webservice: 1
  ADDED   customer_notification: 1
  ADDED   db_ddl: 1
  ADDED   eav: 1
  ADDED   full_page: 0
  ADDED   graphql_query_resolver_result: 1
  ADDED   layout: 1
  ADDED   reflection: 1
  ADDED   translate: 1
DIFF config.php cache_types (before -> after):
  (no change)

14 values that the operator deliberately put in the shared, committed file are now also in the per-environment file, which wins the merge. From this point on, changing any of those 14 values in config.php has no effect on this installation, silently, with no output saying so. A probe reading both layers separately shows the shadowing directly:

PROBE_MERGED_COUNT=15
PROBE_CONFIG_PHP=count=15
PROBE_ENV_PHP=count=15
PROBE_TYPE full_page   declared=Y merged=0 isEnabled=0 cfgphp=1 envphp=0

Step 6 does not undo it. cache:enable full_page flips the one value back and leaves all 15 entries in env.php:

CMD: bin/magento cache:enable full_page
EXIT: 0
Changed cache status:
                     full_page: 0 -> 1
Cleaned cache types:
full_page
DIFF env.php cache_types (after disable -> after enable):
  CHANGED full_page: 0 -> 1
DIFF config.php cache_types (after disable -> after enable):
  (no change)

The only way back to the previous state is to edit app/etc/env.php by hand and delete the section. cache:disable with no arguments followed by cache:enable with no arguments behaves the same way: all 15 entries stay in env.php afterwards.

Additional information

The mechanism is that the write is not scoped to what changed. Magento\Framework\App\Cache\Manager::setEnabled() collects the types it actually touched into $changedStatusTypes, and then calls a single $this->cacheState->persist() that ignores that list:

php
if ($isUpdated) {
    $this->cacheState->persist();
}
return $changedStatusTypes;

Magento\Framework\App\Cache\State::persist() (lib/internal/Magento/Framework/App/Cache/State.php:105) writes the entire in-memory status map, which is the merged view of both files, into env.php:

php
public function persist(): void
{
    $this->load();
    $this->writer->saveConfig([ConfigFilePool::APP_ENV => [self::CACHE_KEY => $this->statuses]]);
}

So every value that arrived from config.php is written out to env.php as a side effect of changing an unrelated type.

A proposal that keeps the current CLI behaviour intact: give StateInterface a way to persist only the types whose status changed, and have Manager::setEnabled() pass $changedStatusTypes to it. Writer::saveConfig() already merges with array_replace_recursive() against the current contents of the target file, so persisting a one-key cache_types array produces the correct env.php for the classic case where the whole map already lives there, and leaves the other types resolving from config.php in the split case. State::persist() can stay as it is for backward compatibility with any caller that relies on it.

This matters beyond the split layout, because it is a one-way door: the promotion happens on any environment where somebody runs a single cache:disable, it is not visible in the command output, and nothing short of hand-editing env.php reverses it.

Release note

Fixed an issue where disabling or enabling a single cache type wrote the complete cache type map into app/etc/env.php, permanently overriding values configured in app/etc/config.php.

Triage and priority

  • Severity: S2 - Affects non-critical data or functionality and forces users to employ a workaround.