I recently had to upgrade an older legacy Laravel system to bring it up to date. When I do these upgrades, I’ll generally follow the instructions on the Laravel upgrade pages, and then I’ll go through the git diff between the versions, and update my system to be as accurate to a brand new Laravel install as possible. You don’t need to do this, this is just my personal preference.
One of the changes that I needed to do was update the phpunit.xml file so that instead of using the <server> element, we now use the <env> element. This had a strange effect of now causing a lot of the automated tests to now fail.
Switching back to <server> would fix it. Comparing the values in the project’s .env.testing file I noticed that there were some differences, changing the items in the phpunit.xml file to those in the .env.testing file would fail again.
After investigating the priority order is:
| Priority | Source | Overwrites? |
|---|---|---|
| Highest | config()->set('key', 'value'); | Yes, everything at run time |
| High | phpunit.xml <server> | Overwrites <env>, .env and .env.* files |
| Medium | phpunit.xml <env> | Overwrites .env and .env.* files |
| Low | .env.* | Replaces the default .env file |
| Lowest | .env | Default |
Now why does this matter? Setting a <server> variable will update the PHP $_SERVER superglobal value, and likewise setting a <env> will update the PHP $_ENV superglobal value. Both of these will overwrite your .env variables, so who cares?
The issue comes when you have multiple different environment files. You’ve got your local testing file, .env.testing and your continuous integration one .env.ci, and maybe you have some more. If any of these files need a different value for the same key, then the custom .env.* file would only update an <env> variable, and not the <server> ones!
It’s also worth noting that when using a .env.* that the file replaces the file entirely, it isn’t a merge like the Laravel config files can do. This means that your .env.* file needs to be complete, not a diff of keys you want to override.
Before loading your application’s environment variables, Laravel determines if an
APP_ENVenvironment variable has been externally provided or if the--envCLI argument has been specified. If so, Laravel will attempt to load an.env.[APP_ENV]file if it exists. If it does not exist, the default.envfile will be loaded.
The phpunit.xml file is still loaded regardless of what .env.* file is loaded, all the --env (or APP_ENV) does is decide which .env.* file gets loaded at the bottom of the priority table. Since PHPUnit sets its <server> and <env> values into the superglobals before Laravel’s environment loading even runs, and Laravel won’t overwrite a variable that already exists, anything defined in phpunit.xml still wins regardless of which .env.* file --env points at. It only changes the values for keys that phpunit.xml doesn’t already define.
And let’s not forget about config()->set('key', 'value') this will take priority over any of the others, allowing you to test multiple different scenarios in your automated tests.