You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The replaceConfigRecursivelyFrom method in Illuminate\Support\ServiceProvider has its arguments in the wrong order, causing published application configs to be overridden by vendor package defaults instead of the other way around.
Imagine you go to a shop to replace your car tires:
You bring new tires (your published config)
The shop has old tires (package defaults)
Current behavior: The shop charges you for new tires but installs the old ones. That's what replaceConfigRecursivelyFrom does right now - it claims to "replace" but keeps the package defaults instead of your published overrides.
Expected behavior: Install YOUR new tires, keep the old ones as backup for missing parts.
Why This Matters
Breaking user expectations: When users publish and modify configs, they expect their changes to be respected
Inconsistent with Laravel patterns: config:publish suggests user configs should override package defaults
Silent failures: No error/warning when user configs are ignored, leading to confusion and wasted debugging time
Memory issues: Users may set conservative limits in published configs (e.g., batch sizes, cache TTLs) to prevent OOM errors, but these are silently ignored
Comparison with mergeConfigFrom
While mergeConfigFrom intentionally prioritizes package defaults (as confirmed in #12159), replaceConfigRecursivelyFrom is semantically different:
merge = combine both, package provides defaults for missing keys
replace = user config takes precedence, package fills gaps
The word "replace" in the method name implies user configs should win, not lose.
Proposed Fix
protectedfunctionreplaceConfigRecursivelyFrom($path, $key)
{
if (! ($this->appinstanceof CachesConfiguration && $this->app->configurationIsCached())) {
$config = $this->app->make('config');
$config->set($key, array_replace_recursive(
$config->get($key, []), // App config first (wins on conflicts)require$path// Package config second (fills gaps)
));
}
}
Impact
This is a breaking change for packages currently relying on the buggy behavior. However:
The current behavior contradicts the method's semantic meaning
Users expect published configs to override vendor defaults
Better to fix now in Laravel 12 than carry this bug forward
Steps To Reproduce
Steps to Reproduce
Create a package with config using replaceConfigRecursivelyFrom
Publish the config: php artisan vendor:publish
Modify a scalar value in published config (e.g., set enabled => false)
Observe that the package default value is used instead
This discussion was converted from issue #57238 on October 01, 2025 21:56.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Laravel Version
12.29.0
PHP Version
8.3.23 (NTS)
Database Driver & Version
psql (PostgreSQL) 16.9 (Debian 16.9-1.pgdg110+1)
Description
The
replaceConfigRecursivelyFrom
method inIlluminate\Support\ServiceProvider
has its arguments in the wrong order, causing published application configs to be overridden by vendor package defaults instead of the other way around.Current Behavior
According to PHP's
array_replace_recursive
documentation:This means values from position 2 (app config) override position 1 (package config).
Real-world example:
Expected Behavior
Published application config should ALWAYS take precedence over package defaults. The arguments should be swapped:
The "Tire Replacement" Analogy
Imagine you go to a shop to replace your car tires:
Current behavior: The shop charges you for new tires but installs the old ones. That's what
replaceConfigRecursivelyFrom
does right now - it claims to "replace" but keeps the package defaults instead of your published overrides.Expected behavior: Install YOUR new tires, keep the old ones as backup for missing parts.
Why This Matters
config:publish
suggests user configs should override package defaultsComparison with
mergeConfigFrom
While
mergeConfigFrom
intentionally prioritizes package defaults (as confirmed in #12159),replaceConfigRecursivelyFrom
is semantically different:The word "replace" in the method name implies user configs should win, not lose.
Proposed Fix
Impact
This is a breaking change for packages currently relying on the buggy behavior. However:
Steps To Reproduce
Steps to Reproduce
replaceConfigRecursivelyFrom
php artisan vendor:publish
enabled => false
)Beta Was this translation helpful? Give feedback.
All reactions