Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save CMCDragonkai/50739cd498d2ae5d2075e25528bf8751 to your computer and use it in GitHub Desktop.
Save CMCDragonkai/50739cd498d2ae5d2075e25528bf8751 to your computer and use it in GitHub Desktop.
Environment Variables for PHP Applications #php #configuration

Environment variables for PHP Applications

According to 12 factor app, the best way to configure things for deployment is through environment variables, not necessarily environment variable files, but just any environment variables. The problem is how to get environment variables into PHP. It is quite complicated depending on what you're doing.

The first step is to make sure you're using getenv in PHP for environment variables as the $_ENV array may not be set unless you set variables_order=EGPCS in the INI settings. This is enough for command line PHP applications including the command line PHP server.

If you're running PHP through Apache either via mod_php or CGI, the only way is to use an .htaccess file along the path from the index.php to the document root and to use SetEnv NAME Value directives. Or you put them into the Virtual Host block inside Apache configuration.

If you're using NGINX + PHP-FPM. You have 3 options:

  1. Set clear_env = no inside your FPM pool configuration, and set environment variables in NGINX via env NAME=value; directive in the main context of the NGINX configuration. However this sets the environment variable for all NGINX workers regardless of virtual hosts.
  2. Set them in the pool configuration using the env[NAME] = value directive.
  3. Use the fastcgi_param NAME "value"; directive inside the location block in NGINX. However these variables do not appear in $_ENV nor getenv(), they will be in $_SERVER. This may or may not be acceptable.

If you don't have access to NGINX configuration or PHP-FPM pool configuration, then you cannot set environment variables at all. If you're using a PAAS provider, the provider will often have their own environment variable setting system, and you can use that.

Coupled with the above complexity is the dealing with INI settings, some of which is only available in the system php.ini file. For CGI and FastCGI (and so FPM), you can use .user.ini. For mod_php, you need to use the special directives inside .htaccess, but it must conditional to the mod_php version. Here's an example:

<IfModule mod_php7.c>
  php_value variables_order "EGPCS"
  php_value date.timezone "UTC"
  php_value session.auto_start 0
  php_value session.use_trans_sid 0
  php_value session.use_strict_mode 1
  php_value session.use_cookies 1
  php_value session.use_only_cookies 1
</IfModule>

However for command line applications, launching via php, they will not read the .user.ini. You can force a specific php.ini using the -c option (but then your specific php.ini file will need to set everything, there's no way to achieve INI merging), or you can specify specific INI settings via the -d option.

As you can see, it's shit-show! If you want write a PHP application that can be portable deployed via Apache, NGINX or through command line execution (which is important when you're performing unit tests, local server, ReactPHP or Swoole, or introspecting via a PHP REPL like Psysh), you have a hairy spaghetti of configuration that you need to maintain and remember how it all works. It becomes even more complicated if some of these environment variables needs to be shared with other applications (like client-side SPA using the same variables as the server-side).

To reduce the hairiness, here are some recommendations for portable development:

  1. Don't rely on php.ini nor .user.ini, most settings you care about you can set directly inside PHP using ini_set. These settings are unlikely to changing to according to deployment environment. The ones that do change according to deployment environment, you can code in feature detection. Ultimately this is an inherent complexity with developing portable applications. Avoid conditionals on target platforms, as this is a losing combinatory battle. Or constrain your compatibility offer of your service.
  2. Use 1 single environment variable file that is compatible with all desired environments. This could be a shell file with a number of export commands that can be used with direnv, or write scripts that source this file before running the command you want to run. Make sure this file is not ignored by revision control system. Becareful when exposing shell commands to be utilised at production (such as for Cron), these commands may require the environment variables to be loaded prior, you can do this in Cron via source .envrc && command-to-be-run.
  3. Use compilation macros to compile for a target environment rather than expecting environment variables during runtime. This actually works nicely when you're already doing this when working on client side SPA like React. The create-react-app project inlines environment variables, because you're not going to get environment variables when running in the browser. But where do you get compilation macros for PHP!? You can use the Yay library: https://github.com/marcioAlmada/yay
  4. Ok so you still need to pass environment variables at runtime. The only foolproof method is to write a transpiler between your source environment variable format (which as mentioned above could be a shell configuration file with export commands), to your target environment variable format. The most portable would be a PHP associative array, that your application looks for at startup. This is not that different from compilation macros mentioned above, but instead of inlining each variable, you're just putting them in an abstract location, but this is still a "compile time" technique.
  5. Wrap everything up into a "compile to environment" script, and integrate this into your deployment flow. Different environments requires different sets of environment variables, you can set this up with a naming pattern like .envrc*, where .envrc is your default, and .envrc.* is for * environment. This works even if your target environment cannot run the "compilation" process either because it is too resource limited, or it is too constrained.
  6. The built artifacts should also be ignored by your revision control system, and it will ignore any built or inlined environment variables.
  7. Implement proper secrets management via delegation and capability based security. Production environments can be injected only through the deployment process, where the developer never has access to them.
  8. Don't forget about the PATH environment variable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment