DOTENV simple alternative

Hi,

Yii2 is missing DOTENV support (meaning setting environment type optionally with few env variables) per application. Issue here is, that constants like YII_ENV, YII_DEBUG are hard-coded in the framework core before configuration loaded - which makes it hard to override them.

Yii3 will have DOTENV support. I understand that this will be done via extern library with composer dependency (correct me if I am wrong). Here I would like to suggest simpler & more performative approach without dependencies (also applicable to Yii2 basic & advanced apps). Here are 4 steps to apply it:

  1. create DOTENV file in app root, e.g. /app/.env with content:
; Current environment type - dev|test|prod
ENVIRONMENT = "dev"

; Note: debug should not be enabled in production
DEBUG = true
  1. create file /app/config/Configurator.php with content:
namespace app\config;

class Configurator
{
	/**
	* Initiate environment
	*/
	public static function init()
	{
		require(__DIR__ . '/../vendor/autoload.php');
		self::setEnvironment();
		require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
	}

	/**
	* Load environment type from dot env file, if exists
	* If not found default framework settings will apply (ENV=prod, DEBUG=false)
	*/
	protected static function setEnvironment()
	{
		// check for dot env file
		if(is_file(__DIR__ . '/../.env')){

			$conf = parse_ini_file(__DIR__ . '/../.env', true);
			if(false === $conf){
				throw new \Exception("Failed parsing [.env] file.");
			}

			if(!empty($conf['ENVIRONMENT'])){
				switch(strtolower($conf['ENVIRONMENT'])){
					case 'prod':
					case 'production':
						defined('YII_ENV') || define('YII_ENV', 'prod');
						defined('YII_DEBUG') || define('YII_DEBUG', false);
						break;
					case 'test':
					case 'testing':
					case 'staging':
						defined('YII_ENV') || define('YII_ENV', 'test');
						break;
					case 'dev':
					case 'devel':
					case 'development':
						defined('YII_ENV') || define('YII_ENV', 'dev');
						break;
				}
			}

			if(isset($conf['DEBUG'])){
				defined('YII_DEBUG') || define('YII_DEBUG', !!$conf['DEBUG']);
			}
		}
	}
}


  1. Adjust /app/web/index.php:

< ?php

// set environment and debug mode
require DIR . ā€˜/ā€¦/app/config/Configurator.phpā€™;
\app\config\Configurator::init();

$config = require(DIR . ā€˜/ā€¦/app/config/main.phpā€™);
(new yii\web\Application($config))->run();

  1. Adjust console CLI /app/yii:

#!/usr/bin/php
< ?php
// fcgi doesnā€™t have STDIN and STDOUT defined by default
defined(ā€˜STDINā€™) or define(ā€˜STDINā€™, fopen(ā€˜php://stdinā€™, ā€˜rā€™));
defined(ā€˜STDOUTā€™) or define(ā€˜STDOUTā€™, fopen(ā€˜php://stdoutā€™, ā€˜wā€™));

// set environment and debug mode
require DIR . ā€˜/config/Configurator.phpā€™;
\app\config\Configurator::init();

$config = require(DIR . ā€˜/config/console.phpā€™);

$application = new yii\console\Application($config);
$exitCode = $application->run();
exit($exitCode);

PROS:

  • no dependency
  • simple & flexible (you can adjust the Configurator class with your own logic)
  • fast - quick native PHP parser for INI format

CONS:

  • no other formats supported (YAML, XML) - seems overkill for 2-4 constants

What do you think?
Thanx.

1 Like

If I understand what youā€™re describing right then I think we do something basically equivalent.

  • We have a file constants.php thatā€™s kept in @basePath and is excluded from git.
  • The web entry script requires constants.php (fatal error if not found).
  • Server provisioning configures a constants.php file suitable for each specific environment and puts it in place.

Thatā€™s it. What more do you need?

In our case we use SaltStack for provisioning. It uses a Jinja template to write out a constants.php to each server/container/vm with the values it should use. But itā€™s perfectly reasonable in my opinion to provision such a file manually if you have a simple setup.

I havenā€™t found it necessary to use such a constants file for console commands. We pass environment-specific values as command line opts/args. The commands are run by systemd, using unit files templated out by SaltStack, or by other PHP scripts.

We currently set about a dozen values in constants.php: the classic YII consts, control of Rollbar or Stripe test mode, names/addresses of certain services, miscellaneous stuff like that, but it could be a lot more. I donā€™t see why not so long as you keep organized.

Actually, if you template your constants.php (as we do) then you can keep env-specific values in yaml, XML, JSON or whatever you like. Many of ours are in the Salt Pillar in yaml.

We also have a constants-example.php file that never runs but that is in git and serves to define and document what can go in constants.php within the team.

Just to clarify - this is solution suggested for Yii3 to alleviate issues in Yii2 basic & advanced app, since their architecture by default does not allow setting environment-dependent constants before loading configuration. This has been discussed at many places and still is not solved - e.g. discussion here. Environment needs to be set consistently for both HTTP requests and well as CLI commands. Suggested approach attempts to avoid 3rd party dependency while retaining flexibility and minimum overhead for Yii3.
If you use same approach, just with other names e.g. constants.php - itā€™s fine - youā€™ve got the idea :wink:

I donā€™t see how their architecture prevents setting environment-dependent constants before loading configuration. I used the Yii 2 basic app and added require(__DIR__ . './rel/path/constant.php') on line 2 here

and it works fine.

1 Like

Well, this is not about you or me.
I solved the issue long time ago in my projects.
Itā€™s about finding generally accepted solution for Yii3.
If you believe that your solution can be generally accepted then make please PR for Yii3 core - see if it will be accepted by Yii team :slight_smile:

Iā€™ve now looked at vlucas/phpdotenv and I would not be happy if I had to deal with a dependency like that to deliver local config.

It suggests to me that this may be one of those areas of functionality that doesnā€™t lend itself well to a common solution. For an ORM, everyone can more-or-less adapt themselves to one thing whereas something like phpdotenv has to adapt itself to every thing. And everyone already has quite diverse requirements, experience and preferences. So it ends up being more complex than many applications need.

So, with respect to your suggestion of submitting a PR, mine would be to remove this and replace it with a comment at the top of the demo appā€™s entry script, maybe with some text for the docs to elaborate. Do you think that could fly?

Btw: I think this topic can usefully be approached together with provisioning secrets.

Iā€™d prefer @thefsb solution plus some docs on how config could be approached.

OK guys, if you prefer documenting + letting each user adjust his own code rather than out-of-box solution then itā€™s fine. I just wanted to open the question here, so it can be considered as already discussed for the purpose of Yii3.

@thefsb Exactly, vlucas/phpdotenv would be overkill ā€¦

Thanx for your ideas.

Ok. Iā€™ll prepare something.

The demo will work ootb no more or less than with something more complicated. The nature of custom local configs means the user has to do whatever is needed.

Because he does not use composer-config-plugin, its very simple support DOTENV, or $params, working in YII2, YII3.

Without reading all the above ā€¦ Dot env is meant for development, not production.

Itā€™s being mis-used. I would guess that 70% of laravel sites on shared hosting are not secure because of that dot env file. People refuse to install main Yii and main Laravel above public_html.

You can put constants in the index file:
I.E.:

defined('DS') || define('DS', DIRECTORY_SEPARATOR);
define('DIR', DS . 'yii2' . DS);
define('PREFIX', 'dc_');

require __DIR__ . '/../../yii2up/vendor/autoload.php';
require __DIR__ . '/../../yii2up/vendor/yiisoft/yii2/Yii.php';
$config = require __DIR__ . '/../../yii2up/config/web.php';

(new yii\web\Application($config))->run();

Nothing wrong with the regular php way of doing things.

Yes, since DOTENV file should not be committed, it should also never get into production.
It helps only to quickly configure development & testing servers.

I think @lubosdz question deserves more attention.
We used to manage our environments with .env file, mostly because we work with docker tech and itā€™s more straightforward setting our global project params (mysql credential,github key, type of env).
Furthermore having a config manager, for .env file too, is more clear than pollute code with inconsistent getenv() call in the code, instead of setting complex php nidificate array.
The flexibility it brings is especially noticed as time passes, at least for me because Iā€™ve just to deal with on single file to setup different environments or specific configurations.

I think developer must choose it self the way how to provide environment constants, depending on business logic that he want to achieve, but we need more documentation and examples on ways how to do this.

For example, for me a more simplistic approach can be that we have an env.php file, ignored from git, with environment defined constants:

<?php
// File: env.php

// local environment constants
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
....

Now in all web/index.php files add the fallowing lines on top of file before all other content:

<?php 

// check for exists of env.php and require it
if (file_exists(__DIR__ . '/path/to/env.php')) {
    require __DIR__ . '/path/to/env.php';
}

// defining default constants in case of missing env.php
defined('YII_DEBUG') or define('YII_DEBUG', false);
defined('YII_ENV') or define('YII_ENV', 'prod');
....

In this way you can define local env constants by creating a env.php file.
So no parser and other libraries for this approach needed.

Now you just need to add an env.php file on dev machines, and this also can be automated.

PS: I prefer to keep things simply as possible :wink:

I think Iā€™m replying too late, but, in Laravel, the env function allows to load or the .env variables or the SetEnv variables in Apache2 vhosts config files (I think Nginx has a similar feature but I donā€™t have a clue) and, it avoid to ā€œburnā€ these data in the config files, so I think an env function for Yii should be useful in a mode.

1 Like

Yeah. We started filling $_ENV in app templates by default from both .env and environment.

1 Like