Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add a new Dotenv component#21234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
fabpot merged 1 commit intosymfony:masterfromfabpot:dotenv
Jan 12, 2017
Merged

Add a new Dotenv component#21234

fabpot merged 1 commit intosymfony:masterfromfabpot:dotenv
Jan 12, 2017

Conversation

@fabpot
Copy link
Member

@fabpotfabpot commentedJan 11, 2017
edited
Loading

QA
Branch?master
Bug fix?no
New feature?yes
BC breaks?no
Deprecations?no
Tests pass?yes
Fixed ticketsn/a
LicenseMIT
Doc PRsymfony/symfony-docs#7350

This introduces a new Dotnv Component that manages.env files. Read the referenced doc PR above for more information about usage:

But here, I want to explain the rationale behind creating such a component instead of reusing an existing one.

  • First, this version only implements what you can do in a "real" bash shell script (which is what a.env really is): sono value validation for instance (and anyway, an env var value is always a string). That's important as in production, we should use real env variables, and we don't have validation for them there;

  • It allows to only parse a file without populating the env variables (we have 3 stages:loadparse andpopulate);

  • Strict implementation of what you can do in a.env file, same behavior as bash ($VAR and ${VAR} are supported for instance, executing commands as well);

  • Great error messages: I spent a lot of time being sure that error reporting is top notch;

  • Clean, simple, and straightforward code (small public API);

  • It only does.env management, there is no uneeded abstractions like being able to add an env variable directly (just useputenv);

There are some unimplemented features as I don't think they are needed and would increase the complexity of the code: several concatenated stringsFOO='foo'"bar" for instance.

linaori, abellion, MatthiasMolitor, yceruto, juuuuuu, Comrade42, felipsmartins, ZhukV, dunglas, jakzal, and 11 more reacted with thumbs up emojihason, HeahDude, rvanlaak, yceruto, Comrade42, felipsmartins, dinamic, viniciusss, markuspoerschke, and fonini reacted with hooray emoji
@simensen
Copy link
Contributor

One issue I've had with most of the.env libraries is that if you create a.env.dist you are forced to copy it to.env before running the application otherwise it throws something like aPathException. What would be the best way to use this such that you don'thave to have a.env defined and only load it if that path exists?

I would be easy enough to usetry/catch, but might not be as elegant. With an application using something likeapp.php vsapp_dev.php, it is easy to just put the.env loader intoapp_dev.php only, but some environments (like Envoyer & Forge) rely on using.env for configuration in production. It could be argued this is wrong (and the docs with this PR clearly state that it shouldnot be used in production...) but not everyone sees it that way.

All-in-all, looks like a reasonable thing to add and reasons for not using prior art directly (proper bash support) seems legit.

AlbertClo reacted with thumbs up emoji

@fabpot
Copy link
MemberAuthor

@simensen That's an excellent question... for which I have some answers. Basically, you need some env vars to be present. So, for a Symfony app, you might have something like this in your web front controller:

if (!getenv('APP_ENV')) {    (newDotenv())->load(__DIR__.'/../.env');}

That way, you can define real env vars and we fallback automatically to a.env file if not present (and there, we do want to enforce it as we do need those env vars).

As you might imagine, this component is a prerequisite for Symfony Flex which automatically copies the.env.dist file to a.env file that you can then customize (during composer update/install); Flex also automatically adds env vars when adding some specific bundles like Doctrine or Swiftmailer. I still have one question in Symfony Flex: whether we want a.env file under the root directory or if we want anenv file (not that it does not start with a dot) under the config directory for easy discovery (as some/most IDEs do not list dot files). I would prefer the second option but this is "less standard".

simensen reacted with thumbs up emoji

@dzuelke
Copy link
Contributor

Please no using .dist automatically! If an app needs default values then those can be achieved using the 3.2 functionality for env.

@fabpot
Copy link
MemberAuthor

@dzuelke What do you mean by "automatically"?

@fabpotfabpotforce-pushed thedotenv branch 5 times, most recently from3261280 to7c0cb18CompareJanuary 11, 2017 03:30
array('FOO=" "', array('FOO' => ' ')),
array("FOO=\"bar\nfoo\"", array('FOO' => "bar\nfoo")),

// concataned values

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

concataned ->concatenated ?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

fixed

*
* @param array An array of env variables
*/
public function populate($values)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

missingarray typehint.

$this->cursor += 7;
}

$this->skipWhitespace();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

could be moved in theif block

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

done

$this->skipComment();
} elseif ('"' === $this->data[$this->cursor]) {
++$this->cursor;
while ('"' !== $this->data[$this->cursor] || '\\' === $this->data[$this->cursor - 1]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What if my value really finish with an\ like inexport FOO="BAR\\"? or a concrete examplePATH="c:\\"

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

fixed

@linaori
Copy link
Contributor

I have the feeling thatload(),populate() andparse() should be in a different class. I've got 0 experience with.env so I can't really comment on when you would use it, but I have the feeling this won't be in a service container anyway.

What about some static helper methods like creating a request if it's in the front controller?

Dotenv::load(...);Dotenv::populate(...);Dotenv::parse(...);

The parser itself should not be in the same class as the above 3 methods imo, it feels weird because the class has a state but the same instance can be re-used.

angelov reacted with thumbs up emoji

@hhamon
Copy link
Contributor

The parser/lexer methods should be moved to a separate DotEnvParser/DotEnvLexer class as@iltar suggests. We can keep the Dotenv class for loading a file and populating the global variables.

*/
final class Dotenv
{
const VARNAME_REGEX = '[A-Z][A-Z0-9_]*';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

In my experience an env variable can be lovercase. In my laptop:

$ env|egrep"^[a-z_]+="rvm_bin_path=/home/goetas/.rvm/bin_system_type=Linuxrvm_path=/home/user/.rvmrvm_prefix=/home/user_system_arch=x86_64_system_version=14.04rvm_version=1.26.11 (latest)_system_name=Ubuntu_=/usr/bin/env

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This is correct, I'm using some lower-case as well.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

fixed

@rvanlaak
Copy link
Contributor

So if I understand Flex correctly, an enhancement would be that the component automatically adds*.env to the.gitignore file?

Next to that, will the values eventually be sent to the Opcache, or will they be part of the compiled container?

private $end;
private $state;
private $export;
private $values;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Could you add an accessor for thevalues property?
Reason being I very often don't populate back the environment ($_ENV,$_SERVER, etc) but instead just use as a configuration param bag.

LauLaman reacted with thumbs up emojijakzal reacted with thumbs down emoji
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Where do you want to access it? As it would be accessible via therequest object already.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What (is the)request object?

Right now there is aDotenv::populate() method which gets the parsed values and populate them into the environment.
I would like to be able to read the values being parsed from the.env file without having to modify my environment (read: $_ENV, $_SERVER), through something likeDotenv::getValues()

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Useparse then, it returns the parsed values without populating them.

if ($this->cursor === $this->end) {
if ($this->export) {
throw new FormatException('Unable to unset an environment variable', $this->createFormatExceptionContext());
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

No else case required to throw the next condition

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

done

@King2500
Copy link
Contributor

MaybeDotEnv would be more clear thanDotenv ?

linaori, yceruto, juuuuuu, derrabus, King2500, and AlbertClo reacted with thumbs up emoji

* @throws FormatException when a file has a syntax error
* @throws PathException when a file does not exist or is not readable
*/
public function load(/*...$paths*/)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What about an array instead of variadic? Arrays are more easy to manipulate, validate and so on.. compared to multiple arguments functions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

In php7 (not hhvm!) you can actually doload(string ...$paths). In fact, this is just an array, just likefunc_get_args()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I was talking more about invoking a the function... but will result inEnv::load(...$paths);

so it should work 👍

linaori and sgehrig reacted with thumbs up emoji
/**
* Sets values as environment variables (via putenv, $_ENV, and $_SERVER).
*
* Not that existing environment variables are never overridden.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Typo: "Not that" => "Note that"

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

fixed

@dzuelke
Copy link
Contributor

@fabpot I think I misunderstood@simensen's suggestion as "auto fall back to.env.dist if no.env present", so nevermind ;)

)
/x';

$env = array_replace($_ENV, $_SERVER, $this->values);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is rewriting all the$_SERVER values to the$_ENV variable intended here? I can understand why this is done for$this->values.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Indeed, I don't remember why I did that, removed for now.

(\{)? # optional brace
('.self::VARNAME_REGEX.') # var name
(\})? # optional closing brace
/xi';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The regex is evaluated in case-insensitive mode, while it's case sensitive in thelexVarname() method.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

fixed

public function getEnvDataWithFormatErrors()
{
$tests = array(
array('FOO=BAR BAZ', "A value containing spaces must be surrounded by quotes in \".env\" at line 1.\n...FOO=BAR BAZ...\n ^ line 1 offset 11"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

yield FTW.

{
$name = trim(str_replace(array('export ', '\'', '"'), '', $name));

if (!preg_match('/^[A-Z0-9_]+$/i', $name)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Shouldn't this be the same regex as defined in theVARNAME_REGEX constant?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

old code, not used anymore, removed :)

{
"name": "symfony/env",
"type": "library",
"description": "Register environment variables from a .env file",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

"Register" => "Registers"?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

done

@fabpot
Copy link
MemberAuthor

I don't understand why the lexer/parse should be in another class. TheDotenv class is all about parsing a.env file already.

@josegonzalez
Copy link

Any reason why you aren't using an existing library for this? For instance, them1/env library is pretty feature complete and should handle everything you don't already implement.

I'd also say for actuallyloading the.env file, bothmy own library and the one byvlucas should more than handle any use case symfony users have.

@jakzal
Copy link
Contributor

@josegonzalez the explanation is given in this issue's description. Also, both of the loading libraries you referenced require PHP 7 while Symfony still supports PHP 5.5.

@fabpot
Copy link
MemberAuthor

All comments addressed.

@josegonzalez
Copy link

Not speaking on behalf of vlucas, but:

  • Them1/env library addresses the whole "Implement .env file parsing to bash spec" issue.
  • My library does separate file loading, parsing, and population as you might want.
  • Pretty sure my library handles the user-facing portion in a similar manner as to what you are looking for.

The PHP version is a fair concern. I dropped 5.x support because mocking out php functions -apache_getenv - isn't possible until 7.x, though the previous version (2.1.) works just fine and has more or less all the same functionality.

Just curious though, it's obviously your framework so the decision is up to you. Nice to see other frameworks adopting.env support! 👍

@fabpot
Copy link
MemberAuthor

@josegonzalez At first, I wanted to use vlucas library, I found many issues, I started to fix them to submit PRs, and then I realized that I removed almost all the code. So, I decided to start from scratch instead.

Never heard of m1/env before, but at first glance it fails my first criteria which is to stick to only what bash can understand (the readme starts withunlike bash the assignment is pretty relaxed).

Copy link
Member

@nicolas-grekasnicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

LGTM. Did not read the parsing logic in deep details though :)

public function populate($values)
{
foreach ($values as $name => $value) {
if (getenv($name)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

should we check _ENV before to save a fn call? that's what is done in Container when reading env vars.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

done


// optional export
$this->export = false;
if ('export ' === substr($this->data, $this->cursor, 7)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

strpos?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

done

if ($this->cursor + 1 === $this->end) {
break;
}
if ("'" === $this->data[$this->cursor + 1]) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

use !== + break, then move ++ out of indentation?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

ok


private function skipEmptyLines()
{
if (preg_match('/(\n+|^#[^\n]*(\n*|$))+/Asm', $this->data, $match, null, $this->cursor)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Asm => m only (+^at the beginning)? maybe more common?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

nope

throw new \LogicException('Resolving commands requires the Symfony Process component.');
}

$process = new Process('echo '.$matches[0], null, $env);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

$_ENV, from which $env is sourced, is not populated by default on eg Ubuntu.
But Process 3.2 has a method to enable env inheriting, so that $env can be merged automatically.

why "echo" btw?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

changed to use built-in features of Process

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What would you use instead of echo?

}
}

private function resolveCommands($value)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

we should maybe disable commands on Windows, because escaping is hard there, and it might come later?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

done

return substr($matches[0], 1);
}

if (!class_exists('Symfony\Component\Process\Process')) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Process::class

rvanlaak reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

done

@fabpotfabpotforce-pushed thedotenv branch 2 times, most recently from508cb22 to151af7dCompareJanuary 11, 2017 23:17
$this->skipComment();
} elseif ('"' === $this->data[$this->cursor]) {
++$this->cursor;
while ('"' !== $this->data[$this->cursor] || ('\\' === $this->data[$this->cursor - 1] && '\\' !== $this->data[$this->cursor - 2])) {
Copy link
Member

@jderussejderusseJan 11, 2017
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Shouldn't we handle escaping? Or it doesn't worst it?

  • "Foo" => OK
  • "Foo\" => KO
  • "Foo\\" => OK
  • "Foo\\\" => KO <= this (edge) case look not handled by your implementation.

edit: github strip slashes in my previous comment

Copy link
MemberAuthor

@fabpotfabpotJan 11, 2017
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Not sure it's worth it :)

@fabpotfabpotforce-pushed thedotenv branch 2 times, most recently from2b289e0 to117f405CompareJanuary 11, 2017 23:40
@fabpot
Copy link
MemberAuthor

failing tests not related

public function populate($values)
{
foreach ($values as $name => $value) {
if (isset($_ENV[$name]) || false !== getenv($name)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Shouldn't we check for$_SERVER[$name] too?

Copy link
Member

@nicolas-grekasnicolas-grekasJan 12, 2017
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

$_SERVER mixes env + http headers + PHP special keys. This leads to complications that can open security issues. Better not IMHO.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I just think that people may be confused if they use an env var name that is not part of$_ENV, but exists in$_SERVER. It would then still be overridden though that we claim in the docblock that it wouldn't.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

How can you have an env var is$_SERVER but not in$_ENV? I don't think that's possible.

@fabpot
Copy link
MemberAuthor

Regarding the main class name, people refer to this concept asdotenv (see Google). Naming the class DotEnv would mean that the component name would bedot-env which we don't want.

@fabpotfabpot changed the titleAdd a new Env componentAdd a new Dotenv componentJan 12, 2017
@nicolas-grekas
Copy link
Member

👍

1 similar comment
@jakzal
Copy link
Contributor

👍

@fabpotfabpot merged commit5a6be8a intosymfony:masterJan 12, 2017
fabpot added a commit that referenced this pull requestJan 12, 2017
This PR was merged into the 3.3-dev branch.Discussion----------Add a new Dotenv component| Q             | A| ------------- | ---| Branch?       | master| Bug fix?      | no| New feature?  | yes| BC breaks?    | no| Deprecations? | no| Tests pass?   | yes| Fixed tickets | n/a| License       | MIT| Doc PR        |symfony/symfony-docs#7350This introduces a new Dotnv Component that manages `.env` files. Read the referenced doc PR above for more information about usage:But here, I want to explain the rationale behind creating such a component instead of reusing an existing one. * First, this version only implements what you can do in a "real" bash shell script (which is what a `.env` really is): so **no value validation** for instance (and anyway, an env var value is always a string). That's important as in production, we should use real env variables, and we don't have validation for them there; * It allows to only parse a file without populating the env variables (we have 3 stages: `load` `parse` and `populate`); * Strict implementation of what you can do in a `.env` file, same behavior as bash ($VAR and ${VAR} are supported for instance, executing commands as well); * Great error messages: I spent a lot of time being sure that error reporting is top notch; * Clean, simple, and straightforward code (small public API); * It only does `.env` management, there is no uneeded abstractions like being able to add an env variable directly (just use `putenv`);There are some unimplemented features as I don't think they are needed and would increase the complexity of the code: several concatenated strings `FOO='foo'"bar"` for instance.Commits-------5a6be8a [Dotenv] added the component
xabbuh added a commit to symfony/symfony-docs that referenced this pull requestJan 24, 2017
This PR was merged into the master branch.Discussion----------Add docs for the Dotenv componentSeesymfony/symfony#21234Commits-------03fda49 added docs for the env component
@fabpotfabpot deleted the dotenv branchJanuary 25, 2017 19:33
@fabpotfabpot mentioned this pull requestMay 1, 2017
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@javiereguiluzjaviereguiluzjaviereguiluz left review comments

@nicolas-grekasnicolas-grekasnicolas-grekas left review comments

@lyrixxlyrixxlyrixx left review comments

@jderussejderussejderusse left review comments

@xabbuhxabbuhxabbuh left review comments

+7 more reviewers

@renanrenanrenan left review comments

@jakzaljakzaljakzal left review comments

@hhamonhhamonhhamon left review comments

@webda2lwebda2lwebda2l left review comments

@goetasgoetasgoetas left review comments

@linaorilinaorilinaori left review comments

@rvanlaakrvanlaakrvanlaak left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

18 participants

@fabpot@simensen@dzuelke@linaori@hhamon@rvanlaak@King2500@josegonzalez@jakzal@nicolas-grekas@renan@javiereguiluz@lyrixx@webda2l@jderusse@goetas@xabbuh@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp