Maintaining Laravel config files

Main Thread October 22, 2019 • 6 min read

With the release of Laravel 6, I made a change to Shift to default the configuration files to their latest version.

Primarily this was done to improve the user experience. Previously users had to go download the latest versions of the config file and manually compare them to determine the changes.

By defaulting these files and doing so in an atomic commit the changes can be viewed directly as part of the pull request Shift opens. This allows users to view the differences between these files inline and easily backfill (or revert) their customizations.

Secondarily, this aligns with my recommendation to keep configuration files as default as possible.

This comes not only as the creator of Shift, but also as a developer who maintains nearly a dozen Laravel applications.

The configuration files are the most changed files between Laravel versions. This is not just in the major releases. The weekly releases contain changes to the core files. Just since Laravel 6.0 the configuration files have changed 10 times.

For these reasons, all Laravel upgrade Shifts now default the configuration files and advocate keeping these as default as possible to provide a smoother upgrade path for future versions.

The hard truth

Most developers don’t keep these files up-to-date. Although you can likely get away with this for a while, sooner or later you’ll be left scratching your head viewing a cryptic error message.

I understand the common push-back is these files are meant to be changed. That's a totally fair point. My point is more about maintainability.

Often of the changes made by Laravel or the developer are superficial. Outdated comments, changes to defaults, introduction of new environment variables.

Shift can handle the changes made by Laravel. These leaves us with the changes by the developer. And most customizations are unnecessary, or could at least be done another way.

Let’s take a look at a few of the most common customizations I see and alternatives to which leave your application more maintainable.

Leverage environment variables

I see a lot of applications copying or overriding configuration options instead of leveraging environment variables.

For example, a common one is establishing a test database.

'sqlite_testing' => [
    'driver'   => 'sqlite',
    'url'      => env('DATABASE_URL'),
    'database' => storage_path('app/database.sqlite'),
    'prefix'   => '',
    'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],

This adds a new configuration option which will need to be replicated in all future versions of the database.php configuration file.

Instead of adding a new configuration option, you can leverage the existing sqlite configuration option and set the DB_CONNECTION and DB_DATABASE environment variables.

Furthermore, to the point of test configuration, this could be done more cleanly within a .env.testing file or overriding them within the phpunit.xml configuration file.

The cascade of the phpunit.xml configuration over environment configuration allows for specific, minimal configuration of a test environment.

When using artisan, you may also explicitly set the environment using the --env option.

So, when an environment variable is available, you may use it to avoid making unnecessary changes to the configuration files and gain greater flexibility in configuring your application.

Now, some developers prefer to overwrite the default values instead of having to set an environment variable for every environment. I understand this seems like an easier approach. That is, setting a value in one location instead of multiple.

However, this is a short-term tradeoff. While you are only changing this once from a configuration perspective, you are changing it multiple times as you maintain this value across future versions of the framework.

When viewed over the long-term, setting these through an environment variable actually becomes easier.

Custom configuration options

Another common use of the config files are custom values. These are either apps specific or additions to sections.

For example, application specific settings added to the app.php configuration file, database drivers added to database.php configuration file, or additional services added to service.php configuration files.

All of these are logical locations to add such customizations. But these still have to be carry between each of the upgrades. For those reasons I attempt to separate these values where I can.

Looking back on these examples, let's start with the app.php configuration file. The most common change here are registering providers and aliases. However, with package discovery, these should be minimal.

This leaves truly application specific settings. I like to put these in my own domain specific config file. Sometimes I’ll call this settings.php or use the app name.

For example, Shift has a shift.php configuration file.

return [

    'executable' => env('SHIFT_SCRIPT_PATH', '/opt/shift/main.php'),

    'webhook_executable' => env('WEBHOOK_SCRIPT_PATH', '/opt/shift/webhook.php'),

    'support_email_address' => env('SHIFT_SUPPORT_EMAIL_ADDRESS'),

    'latest_sku' => env('SHIFT_LATEST_SKU'),

    'services' => [

        'github' => [
            'shift_username' => env('GITHUB_USERNAME'),
            'client_id' => env('GITHUB_CLIENT_ID'),
            'client_secret' => env('GITHUB_CLIENT_SECRET'),
            'redirect' => env('GITHUB_CALLBACK_URL'),
        ],

        'gitlab' => [
            'shift_user_id' => env('GITLAB_USER_ID'),
            'client_id' => env('GITLAB_APP_ID'),
            'client_secret' => env('GITLAB_SECRET'),
            'redirect' => env('GITLAB_CALLBACK_URL'),
        ],

        'bitbucket' => [
            'shift_username' => env('BITBUCKET_USERNAME'),
            'client_id' => env('BITBUCKET_KEY'),
            'client_secret' => env('BITBUCKET_SECRET'),
            'redirect' => env('BITBUCKET_CALLBACK_URL'),
        ],

    ]
];

Within this custom configuration file, I set not only app specific configuration values, but also options you might expect to see within something like services.php.

This may seem foreign. But this file is loaded just like any other configuration file. As such, I can reference these values using the config() from anywhere in my application. For example, config('shift.services.gitlab.redirect').

Using a separate config file also means I don't have to worry about maintaining these values within one of the core config files. Again, these files are ever changing and there's no guarantee even core configuration options will remain. For example, the Stripe service configuration was removed from the services.php configuration file in Laravel 6.

So always remember you are free to relocate configuration options to a custom configuration file to improve the maintainability of your application.

For some integrated services like database or logging drivers, you may not have this option. You will need make customizations to the specific core configuration file.

An easier future

All of these recommendations are aimed at improving the developer experience as it relates to maintaining your Laravel application.

As always, I will continue to improve Shift to spot customizations and attempt to backfill them.

Yet, making these small adjustments to your development process now will help you craft a more maintainable Laravel application for the future.

Find this interesting? Let's continue the conversation on Twitter.