laravel / ideas

Issues board used for Laravel internals discussions.
939 stars 28 forks source link

mergeConfigFrom does not merge recursively #2572

Open scottw-finao opened 3 years ago

scottw-finao commented 3 years ago

I was creating a package and trying to add some additional custom log channels for my package so that it would log to a separate couple of files (when in debug mode, this package's logging is very noisy and it talks to multiple webservices so I wanted to separate the logs for each service to make it easier to track problems while still testing)

The problem was, that I couldn't just create a logging.php with a 'channels' => [] configuration in it because the existing mergeConfigFrom() function uses a standard php array_merge which will not merge deeply. So if I add a channel 'mypackage' inside the 'channels' array, the new value never gets merged.

php, however, includes both a array_merge_recursive and array_merge_recursive_distinct. So I added the following to my extension of the ServiceProvider for my package and it seems to work like a champ to deep-merge the configurations:

    /**
     * Merge the given configuration with the existing configuration merging recursive data structures.
     *   extended variation on mergeConfigFrom
     *
     * @param  string  $path
     * @param  string  $key
     * @return void
     */
    protected function mergeConfigRecursiveFrom($path, $key)
    {
        if (! ($this->app instanceof CachesConfiguration && $this->app->configurationIsCached())) {
            $config = $this->app->make('config');

            // use array_merge_recursive instead of just array_merge
            $config->set($key, array_merge_recursive(
                require $path, $config->get($key, [])
            ));
        }
    }

Alternately, if you wanted to make sure it didn't create multiple entries for indexed array datastructures, you could replace the array_merge_recursive with array_merge_recursive_distinct and/or create a separate version mergeConfigRecursiveDistinctFrom()

The way I used this is under my package with a config/logging.php that just includes the new channel data:

return [
    'channels' => [
        'mypackage' => [
            'driver' => 'single',
            'path' => storage_path('logs/mypackage.log'),
            'level' => env('LOG_LEVEL', 'debug'),
        ],
        'customchannel1' => [
            'driver' => 'single',
            'path' => storage_path('logs/custom/customchannel1.log'),
            'level' => env('LOG_LEVEL', 'debug'),
        ],
        'customchannel2' => [
            'driver' => 'single',
            'path' => storage_path('logs/custom/customchannel2.log'),
            'level' => env('LOG_LEVEL', 'debug'),
        ],
        'customchannel3' => [
            'driver' => 'single',
            'path' => storage_path('logs/custom/customchannel3.log'),
            'level' => env('LOG_LEVEL', 'debug'),
        ]
    ]
];

Which I can then load along with the regular package config inside the ServiceProvider::register() method:

        $this->mergeConfigRecursiveFrom(__DIR__ . '/../config/logging.php', 'logging');