DOTENV simple alternative


(Lubosdz) #1

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.


(Fsb) #2

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.


(Lubosdz) #3

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:


(Fsb) #4

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.


(Lubosdz) #5

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:


(Fsb) #6

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.


(Alexander Makarov) #7

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


(Lubosdz) #8

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.


(Fsb) #9

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.


(Wilmer Arambula) #10

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


(Kpsklab) #11

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.


(Lubosdz) #12

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