gmazzap / gea
Environment variables management in PHP
Requires
- php: >=5.5
Requires (Dev)
- gmazzap/andrew: 1.0.*
- mockery/mockery: 0.9.3
- phpunit/phpunit: 4.8.*
This package is auto-updated.
Last update: 2024-10-26 14:34:15 UTC
README
Environment variables management in PHP
From a fork of PHP Dotenv by Vance Lucas.
What's Gea
Gea is a PHP library that manages app configuration via environment variables.
What's environment variables
Environment variables are application configuration that may change according to application environment (production, development, staging...).
A lot of times, these are sensitive configuration, such as database credentials, API keys, and so on.
This kind of configurations, should never be placed in code, for security reasons, but also to be able to easily change them according to environment.
How to store environment variables
Environment variables can be set on the server that runs the application. These can be done via command line, via automated deployment tools (like Capistrano), or configured from an interface provided by the cloud hosting service (like Heroku).
This is very fine, however, during development this is not really handy. Ther's another approach: the .env file.
The .env
file
.env
file is a text file that contains a lists of environment variables, one per line. The way it is written is in bash syntax, something like:
FOO=Bar BAR=Baz
Nested variables
You can even use variables to define other variables:
BASE_PATH=/var/www/app
PUBLIC_PATH=${BASE_PATH}/public
String with spaces
Strings containing spaces, need to be surrounded by quotes:
TITLE="Hello World"
Comments
Any string prepended by #
is a comment
# Following line is super secret PASSWORD=mypassword # Cool, isn't it?
Export
It is also possible to prepend the bash command export
to variables:
export PASSWORD=mypassword
Environment variables and PHP
PHP has two functions to read and write environment variables: getenv()
and putenv()
.
However, with these functions is not possible to set more variables at once, so you would need to parse the .env
file, read it line by line, deal with spaces, quotes, comments....
... no worries, here it comes Gea.
Load .env
file with Gea
To load environmet variables from a file with Gea is very easy.
First we need to obtain an instance of Gea\Gea
class with the static instance()
method.
$gea = Gea\Gea::instance(__DIR__);
Only required parameter is the folder path in which the file is saved. The file name is set as .env
by default, but can be customized to anything passing the name as second argument.
After that, we can call load()
on the obtained instance:
$gea->load();
That's it. All the environment variables in the file are now loaded.
Read environment variables
Surely it is possible to read environment variables using getenv()
PHP function, but Gea comes with a read()
method, and also with ArrayAccess
support.
Following lines do same thing:
$apiKey = $gea->read('APY_KEY'); $apiKey = $gea['APY_KEY']; $apiKey = getenv('APY_KEY');
Write environment variables
As you can guess, Gea also comes with a write()
method, and you can write variables with ArrayAccess
syntax as well:
$gea->write('APY_KEY', 'mysupersecretkey'); $gea['APY_KEY'] = 'mysupersecretkey'; putenv('APY_KEY=mysupersecretkey');
Immutability
Environment variables, by themself, are not immutable. You can change them anytime you want. This may appear fine to many, but it actually add complexity to the application.
For this reason, Gea is committed to immutability.
For example it is not possible to call load()
more than once.
$gea->load(); # ok $gea->load(); # throw an exception
It also means that an environment variables that are already written (or loaded from .env
file) can't be overwritten.
$gea->write('A_VAR', 'A value'); # ok $gea->write('A_VAR', 'Another value'); # throw an exception
Note that Gea does not control putenv()
, so calling that function it will be possible to actually overwrite variables, so immutable behavior only applies when variables are accessed via Gea methods.
However, if you really have to, there are two ways to change a value of variables already set:
- Discarding it
- Hard-flushing it
Discarding variables
It is possible to discard a variable with discard()
Gea method, or simply using unset()
with ArrayAccess
syntax:
$gea->discard('FOO'); unset($gea['FOO']);
After a variable have beed flushed, it can be set again with a different value:
$gea['FOO'] = 'First Value'; echo $gea['FOO']; # print 'First Value' unset($gea['FOO']); # Without this an exception had been thrown on next line $gea['FOO'] = 'Second Value'; echo $gea['FOO']; # Print 'Second Value'
Flushing variables
Gea has two different kinds of variables flushing: hard flush and soft flush.
Soft Flush
Soft flush allows to do more consecutive calls to load()
. However, if the variables loaded on a second call are the same loaded first time, an exception is thrown, because of immutability behavior.
$gea->flush(Gea\Gea::FLUSH_SOFT);
For these reason, soft flush is only useful if all the variables loaded has been discarded, or if the .env
file has changed during app execution... nothing very common.
Hard Flush
Hard flushing can be sees as a sort of bulk discarding, in fact, it can be used to discard more variables at once:
$gea->flush(Gea\Gea::FLUSH_HARD, ['FOO', 'BAR', 'BAZ']);
As you can guess, the second argument of flush()
is an array of the variables that needs to be discarded.
These means that if we may want to know which variables have been set.
Get names of variables set
When variables are loaded from .env
file, the load()
method return an array of the names of all the variables that have been loaded (not the values).
By default this array is not stored anywhere by Gea. But it is possible to instrct Gea to do that by passing the flag Gea\Gea::VAR_NAMES_HOLD
as third argument to instance()
method:
$gea = Gea\Gea::instance(__DIR__, '.env', Gea\Gea::VAR_NAMES_HOLD); $gea->load();
After Gea is instructed to hold variables names, the method varNames()
returns it:
var_export( $gea->varNames() ); // array( 'FOO', 'BAR', 'BAZ' )
The array is kept updated, it means that any variables wrote is added, and any variables discarded is removed.
It also means that thies method in comination with hard flushing can discard all variables loaded:
$gea = Gea\Gea::instance(__DIR__, '.env', Gea\Gea::VAR_NAMES_HOLD); $gea->load(); $gea->flush(Gea\Gea::FLUSH_HARD, $gea->varNames());
Actually, the second argument can be omitted: hard flush defaults to discard all variables if nothing is provided and Gea\Gea::VAR_NAMES_HOLD
flag is used to instantiate Gea.
Read-only behavior
Gea provides a way to disable all write, flush and dicard operation. After variables have been loaded, they can't be changed at all.
It can be done by passing the flag Gea\Gea::READ_ONLY
as third argument to instance()
method:
$gea = Gea\Gea::instance(__DIR__, '.env', Gea\Gea::READ_ONLY); $gea->load();
After that, any call to write()
, discard()
, or flush()
with throw an exception.
Note that instance()
third argument accepts a bitmask of flags, so it is possible to combine them:
$flags = Gea\Gea::READ_ONLY|Gea\Gea::VAR_NAMES_HOLD; $gea = Gea\Gea::instance(__DIR__, '.env', $flags);
Filtering and validating variables
A powerful feature of Gea is filtering and validation of variables.
Environent variables are strings. However, configuration values are not always strings... you may want integers, booleans... and so on.
Moreover, some configuration values are required, and it is fine to "fail early" if those required configuration are not there.
Gea variable filters allow to do all of this.
Filters are added using addFilter()
method of Gea instance. The filters shipped with Gea are:
- required
- enum
- choices
- int
- float
- bool
- array
- object
- callback
and it is possible to write custom filters.
What filters do
When variables are accessed using Gea methods, the returned value can be changed by filters.
Most filters are lazy, it means they are evaluated (only once) when (and if) the value is first accessed.
Filters that do validation like "required" or "enum" are evaluated immediately after variables have been loaded, to ensure an early fail if a mandatory value is missing.
Required variables
To make an environment variable mandatory, it is possible to use the "required" filter, like so:
$gea = Gea\Gea::instance(__DIR__); $gea->addFilter('API_KEY', 'required');
Now when variables are loaded, if the API_KEY
var is not set, an excetion is thrown.
This behavior is not the same for all filters: in fact, only 'required'
, enum
, and choices
are evaluated on load, all other filters are lazy, they are evaluated when (and if) the variable is first accessed.
Constrain variables to some values
Both 'enum'
and 'choices'
filters do pretty the same thing: force the variable to a set of possible values. Only difference is that enum
do a strict comparison (===
) while 'choices'
do a weak comparison (==
).
$gea->addFilter('MY_SWITCH', ['enum' => ['on', 'off']]);
This time I passed the filters as a single item array, where the key is the filter name, and the value is an an array to configure the filter. This is the syntax to use for filters that actually need configuration.
Worth noting that this filter can be used to make a variable required, for example the code above will trigger an exception if the var 'MY_SWITCH'
is not set. By adding null
to the list of values, the variable becomes optional.
Cast to numerical values
The two filters 'int'
and 'float'
are used to ensure the variable they are applied to, is casted to, respectively, an integer and a floating comma number. These filters, just like al the following, are lazy, they are evaluated when and if the variable they are associated is first accessed.
$gea->addFilter('MY_NUMBER', 'int');
If the value is not numerical, the filter trigger an exception.
Cast to booleans
The 'bool'
filter is used to cast variabls to booleans. Nothe that this filters uses filter_var()
in combination with FILTER_VALIDATE_BOOLEAN
constant, it means that '1'
, 'true'
, 'yes'
, 'on'
strings are all casted to true
.
$gea->addFilter('AWESOMENESS_ALLOWED', 'bool');
Make array from variables
The 'array'
filter is very flexible, and is used to convert a variable to an array. In its simplest form just explode the string by commas:
$gea->addFilter('MY_LIST', 'array');
Configuration for this filter acceps three arguments: the first allows to set a different separator, the second can bne used to turn on or off the trim of items after the explode, finally the third argument can be set as a callback that will me used to map all items.
For instance, let's assume the var USER
is set in .env
file like this:
USER=" john | doe"
Using the filter:
$gea->addFilter('USER', ['array' => ['|', ArrayFilter::DO_TRIM, 'ucfirst']]); $gea->load();
Then
var_export($gea['USER']); // array( 'John', 'Doe' )
Instantiate objects from variables
The 'object'
filter can be used to instantiate an object, which class is given when ading filter, passing the current value of the variable to class constructor.
$gea->addFilter('USER_EMAIL', ['object' => [My\App\EmailObject::class]);
Useful to instantiante value objects straight from env variables.
Transformate variables with callback
Possibly the most flexible filter shipped with Gea is callback
, it allows to set a callback that will be called passing as argument the current value of the variable. Whatever the callback returns, will be used as variable value.
$gea->addFilter('USER_ID', ['callback' => [function($userId) { return My\App\UserFactory::fromID($userId); }]);
Combine filters
In Gea it is possible to combine variables filters. When more filters are set to same variable, they are called in order using pipeline pattern: the next filter is called with the result of previous as argument.
To add more filters to a variable, you can:
- call more and more times
addFilter()
using the same variable name - pass an array of filters as
addFilter()
second argument - both the previous
Some examples:
$gea->addFilter('USER_ID', ['required', 'int']);
$gea->addFilter('MY_LIST', ['array', ['object' => [\ArrayIterator::class]]);
$gea->addFilter('USER_EMAIL', ['required', ['callback' => function($email) { return filter_var($email, FILTER_SANITIZE_EMAIL); }]);
Gea for production environments
Loading variables from .env
files works very well for development, however it should be avoided on production, to skip the overhead of loading and parsing the .env
file.
Nice thing about Gea is that you don't have to load variables with it to use it: you can use Gea to access (and optionally to validate and filter) environment variables without ever calling load()
.
Gea will just look for variables, no matter how they are set.
This behavour, combined with the ArrayAccess
interface implemented by Gea, allows to use it as a configuran bucket, something very easy to mock or replace on tests, decoupling the application code from any environment-related operation.
No-loader instance
The easiest way to use Gea as a configuration "container" without load nothing, is to use the the noLoaderInstance()
named constructor, as easy as:
$gea = Gea\Gea::noLoaderInstance();
Not having to load any file, there's no need to pass folder path or file name. Gea flags can passed as first argument, if necessary.
An instance obtained in this way, can do anything that a *normal" Gea instance can do, you can even call load()
on it, and it will load nothing, butany non-lazy filter is immediately evaluated.
No-loader & read-only instance
Another named constructor available in Gea is readOnlyInstance()
. An instance obtained with this method, not only does not load anything, but is also in read-only mode.
$gea = Gea\Gea::readOnlyInstance();
is equivalent to:
$gea = Gea\Gea::noLoaderInstance(Gea\Gea::READ_ONLY);
Integration Example
Let's imagine a pseudo class like this:
namespace MyApp; class App { public function run(\ArrayAccess $configs) { // bootstrap the application here } }
and an index file like this:
$gea = getenv('APP_ENV') === 'production' ? Gea\Gea::noLoaderInstance() # in production we load nothing : Gea\Gea::instance(__DIR__); # in development we load .env file $gea->addFilter(['DB_NAME', 'DB_USER', 'DB_PASS'] 'required'); // maybe more filters here... // This will load nothing on production, but ensures filter are validated $gea->load(); $myApp = new MyApp\App(); $myApp->run($gea);
The App
class code is completely decoupled from environment functions, global variables and even from Gea specific methods. It means that it will be very easy to mock the configuration in tests, or maybe switch to another kind of configuration, like JSON, Yaml or just PHP files.
Why Gea?
Gea was born as a fork of PHP dotenv by Vance Lucas.
That library is widely used and is considered by PHP community as a very affordable piece of code.
So, why this?
Everything started because for an app I was working on, I could not allow environment variables to be stored in $_SERVER
array (that was exposed), that is the default behavior of PHP Dotenv.
I realized that customize this behavior was not as simple as I thought, and decided to fork the library and add an interface that had allowed an easier replacement for the PHP Dotenv Loader
class.
The original idea was to contribute to PHP Dotenv, but after having forked and added the interface, I could not stop myself refactoring... ending up in a complete rewrite with a completely different architecture.
Something that surely can't be merged in a pull request.
Gea architecture is more complex than PHP Dotenv, and that's bad, but it is also more flexible and has more "sugars" that aren't there in PHP Dotenv.
Of course, one can use Gea to just load a .env
file like Dotenv does, without using any other feature or customization; in that case differences with PHP Dotenv are close to zero.
Depending on your use case, Gea may fit or just not.
Some differences between PHP Dotenv and Gea
- Gea has filtering for advanced variable casting and filtering (PHP Dotenv has "required" and "allowedValues" for validation)
- Gea implements
ArrayAccess
,read()
,write()
,discard()
, andflush()
methods that make it usable as a configuration "bucket". This makes Gea more a configuration "bucket" than just a loaded for environment variables This feature are completely decoupled from loader that might not be used at all. - Gea ensures immutability by default, when values are accessed with its methods
- In Gea is easy to have a clue of which variables have been set / loaded
- Gea is completely OOP, it means that implementing its interfaces it's easy to customize its behavior
- Minimum required PHP version: 5.3.9 for PHP Dotenv, 5.5 for Gea
- License is BSD-3-clause for PHP Dotenv, MIT for Gea.
Incorporated parts from PHP Dotenv
This package contains parts of code from PHP Dotenv. More specifically, the parser "engine" is taken pretty much "as is" from there.
Tests fixtures and some test sources comes from PHP Dotenv, to ensure Gea loads files just like Dotenv does.
All the files that incorporates code from PHP Dotenv contain license notice on top.
Minimum Requirements
- PHP 5.5+
Installation
With Composer require gmazzap/gea
.
License
Gea is released under MIT license https://opensource.org/licenses/MIT
Contributing
See CONTRIBUTING.md
.
Don't use issue tracker (nor send any pull request) if you find a security issue. They are public, so please send an email to the address on my Github profile. Thanks.