Repository: vlucas/phpdotenv
Branch: master
Commit: 2af27192fc6c
Files: 91
Total size: 208.1 KB
Directory structure:
gitextract_7d43qxaz/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── SECURITY.md
│ └── workflows/
│ ├── static.yml
│ └── tests.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── UPGRADING.md
├── composer.json
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml.dist
├── src/
│ ├── Dotenv.php
│ ├── Exception/
│ │ ├── ExceptionInterface.php
│ │ ├── InvalidEncodingException.php
│ │ ├── InvalidFileException.php
│ │ ├── InvalidPathException.php
│ │ └── ValidationException.php
│ ├── Loader/
│ │ ├── Loader.php
│ │ ├── LoaderInterface.php
│ │ └── Resolver.php
│ ├── Parser/
│ │ ├── Entry.php
│ │ ├── EntryParser.php
│ │ ├── Lexer.php
│ │ ├── Lines.php
│ │ ├── Parser.php
│ │ ├── ParserInterface.php
│ │ └── Value.php
│ ├── Repository/
│ │ ├── Adapter/
│ │ │ ├── AdapterInterface.php
│ │ │ ├── ApacheAdapter.php
│ │ │ ├── ArrayAdapter.php
│ │ │ ├── EnvConstAdapter.php
│ │ │ ├── GuardedWriter.php
│ │ │ ├── ImmutableWriter.php
│ │ │ ├── MultiReader.php
│ │ │ ├── MultiWriter.php
│ │ │ ├── PutenvAdapter.php
│ │ │ ├── ReaderInterface.php
│ │ │ ├── ReplacingWriter.php
│ │ │ ├── ServerConstAdapter.php
│ │ │ └── WriterInterface.php
│ │ ├── AdapterRepository.php
│ │ ├── RepositoryBuilder.php
│ │ └── RepositoryInterface.php
│ ├── Store/
│ │ ├── File/
│ │ │ ├── Paths.php
│ │ │ └── Reader.php
│ │ ├── FileStore.php
│ │ ├── StoreBuilder.php
│ │ ├── StoreInterface.php
│ │ └── StringStore.php
│ ├── Util/
│ │ ├── Regex.php
│ │ └── Str.php
│ └── Validator.php
├── tests/
│ ├── Dotenv/
│ │ ├── DotenvTest.php
│ │ ├── Loader/
│ │ │ └── LoaderTest.php
│ │ ├── Parser/
│ │ │ ├── EntryParserTest.php
│ │ │ ├── LexerTest.php
│ │ │ ├── LinesTest.php
│ │ │ └── ParserTest.php
│ │ ├── Repository/
│ │ │ ├── Adapter/
│ │ │ │ ├── ArrayAdapterTest.php
│ │ │ │ ├── EnvConstAdapterTest.php
│ │ │ │ ├── PutenvAdapterTest.php
│ │ │ │ └── ServerConstAdapterTest.php
│ │ │ └── RepositoryTest.php
│ │ ├── Store/
│ │ │ └── StoreTest.php
│ │ └── ValidatorTest.php
│ └── fixtures/
│ └── env/
│ ├── assertions.env
│ ├── booleans.env
│ ├── commented.env
│ ├── empty.env
│ ├── example.env
│ ├── exported.env
│ ├── immutable.env
│ ├── integers.env
│ ├── large.env
│ ├── multibyte.env
│ ├── multiline.env
│ ├── multiple.env
│ ├── mutable.env
│ ├── nested.env
│ ├── quoted.env
│ ├── specialchars.env
│ ├── unicodevarnames.env
│ ├── utf8-with-bom-encoding.env
│ └── windows.env
└── vendor-bin/
└── phpstan/
└── composer.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
================================================
FILE: .gitattributes
================================================
* text=auto
/tests export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/.github export-ignore
/Makefile export-ignore
/phpstan-baseline.neon export-ignore
/phpstan.neon.dist export-ignore
/phpunit.xml.dist export-ignore
/README.md export-ignore
/UPGRADING.md export-ignore
/vendor-bin export-ignore
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# CONTRIBUTOR COVENANT CODE OF CONDUCT
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
hello@gjcampbell.co.uk.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
================================================
FILE: .github/CONTRIBUTING.md
================================================
# CONTRIBUTION GUIDELINES
Contributions are **welcome** and will be fully **credited**.
We accept contributions via pull requests on Github. Please review these guidelines before continuing.
## Guidelines
* Please follow the [PSR-12 Coding Style Guide](https://www.php-fig.org/psr/psr-12/).
* Ensure that the current tests pass, and if you've added something new, add the tests where relevant.
* Send a coherent commit history, making sure each individual commit in your pull request is meaningful.
* You may need to [rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) to avoid merge conflicts.
* If you are changing or adding to the behaviour or public api, you may need to update the docs.
* Please remember that we follow [SemVer](https://semver.org/).
## Running Tests
First, install the dependencies using [Composer](https://getcomposer.org/):
```bash
$ make install
```
Then run [PHPUnit](https://phpunit.de/) and the static analyzers:
```bash
$ make test
```
These will also be automatically run by [GitHub Actions](https://github.com/features/actions) against pull requests.
================================================
FILE: .github/FUNDING.yml
================================================
github: GrahamCampbell
tidelift: "packagist/vlucas/phpdotenv"
================================================
FILE: .github/SECURITY.md
================================================
# SECURITY POLICY
## Supported Versions
After each new major release, the previous release will be supported for no
less than 2 years, unless explicitly stated otherwise. This may mean that there
are multiple supported versions at any given time.
## Reporting a Vulnerability
If you discover a security vulnerability within this package, please send an
email to security@tidelift.com. All security vulnerabilities will be promptly
addressed. Please do not disclose security-related issues publicly until a fix
has been announced.
================================================
FILE: .github/workflows/static.yml
================================================
name: Static Analysis
on:
push:
pull_request:
jobs:
phpstan:
name: PHPStan
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
tools: composer:v2
coverage: none
env:
update: true
- name: Install Dependencies
uses: nick-fields/retry@v3
with:
timeout_minutes: 5
max_attempts: 5
command: composer update --no-interaction --no-progress
- name: Install PHPStan
uses: nick-fields/retry@v3
with:
timeout_minutes: 5
max_attempts: 5
command: composer bin phpstan update --no-interaction --no-progress
- name: Execute PHPStan
run: vendor/bin/phpstan analyze --no-progress
================================================
FILE: .github/workflows/tests.yml
================================================
name: Tests
on:
push:
pull_request:
jobs:
latest:
name: PHP ${{ matrix.php }} Latest
runs-on: ubuntu-24.04
strategy:
matrix:
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- name: Setup Problem Matchers
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Install Latest Dependencies
uses: nick-fields/retry@v3
with:
timeout_minutes: 5
max_attempts: 5
command: composer update --no-interaction --no-progress
- name: Execute PHPUnit
run: vendor/bin/phpunit
lowest:
name: PHP ${{ matrix.php }} Lowest
runs-on: ubuntu-24.04
strategy:
matrix:
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- name: Setup Problem Matchers
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Install Lowest Dependencies
uses: nick-fields/retry@v3
with:
timeout_minutes: 5
max_attempts: 5
command: composer update --prefer-lowest --prefer-stable --no-interaction --no-progress
- name: Execute PHPUnit
run: vendor/bin/phpunit
================================================
FILE: .gitignore
================================================
.phpunit.result.cache
composer.lock
phpstan.neon
phpstan.tests.neon
phpunit.xml
vendor
================================================
FILE: LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2014, Graham Campbell.
Copyright (c) 2013, Vance Lucas.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: Makefile
================================================
install:
@docker run -it -w /data -v ${PWD}:/data:delegated -v ~/.composer:/root/.composer:delegated --entrypoint composer --rm registry.gitlab.com/grahamcampbell/php:8.5-base update
@docker run -it -w /data -v ${PWD}:/data:delegated -v ~/.composer:/root/.composer:delegated --entrypoint composer --rm registry.gitlab.com/grahamcampbell/php:8.5-base bin all update
phpunit:
@docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpunit --rm registry.gitlab.com/grahamcampbell/php:8.5-cli
phpstan-analyze:
@docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpstan --rm registry.gitlab.com/grahamcampbell/php:8.5-cli analyze
phpstan-baseline:
@docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpstan --rm registry.gitlab.com/grahamcampbell/php:8.5-cli analyze --generate-baseline
test: phpunit phpstan-analyze
clean:
@rm -rf .phpunit.result.cache composer.lock vendor vendor-bin/*/composer.lock vendor-bin/*/vendor
================================================
FILE: README.md
================================================
PHP dotenv
==========
Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.

**Special thanks to [our sponsors](https://github.com/sponsors/GrahamCampbell)**
## Why .env?
**You should never store sensitive credentials in your code**. Storing
[configuration in the environment](https://www.12factor.net/config) is one of
the tenets of a [twelve-factor app](https://www.12factor.net/). Anything that
is likely to change between deployment environments – such as database
credentials or credentials for 3rd party services – should be extracted from
the code into environment variables.
Basically, a `.env` file is an easy way to load custom configuration variables
that your application needs without having to modify .htaccess files or
Apache/nginx virtual hosts. This means you won't have to edit any files outside
the project, and all the environment variables are always set no matter how you
run your project - Apache, Nginx, CLI, and even PHP's built-in webserver. It's
WAY easier than all the other ways you know of to set environment variables,
and you're going to love it!
* NO editing virtual hosts in Apache or Nginx
* NO adding `php_value` flags to .htaccess files
* EASY portability and sharing of required ENV values
* COMPATIBLE with PHP's built-in web server and CLI runner
PHP dotenv is a PHP version of the original [Ruby
dotenv](https://github.com/bkeepers/dotenv).
## Installation
Installation is super-easy via [Composer](https://getcomposer.org/):
```bash
$ composer require vlucas/phpdotenv
```
or add it by hand to your `composer.json` file.
## Upgrading
We follow [semantic versioning](https://semver.org/), which means breaking
changes may occur between major releases. We have upgrading guides available
for V2 to V3, V3 to V4 and V4 to V5 available [here](UPGRADING.md).
## Usage
The `.env` file is generally kept out of version control since it can contain
sensitive API keys and passwords. A separate `.env.example` file is created
with all the required environment variables defined except for the sensitive
ones, which are either user-supplied for their own development environments or
are communicated elsewhere to project collaborators. The project collaborators
then independently copy the `.env.example` file to a local `.env` and ensure
all the settings are correct for their local environment, filling in the secret
keys or providing their own values when necessary. In this usage, the `.env`
file should be added to the project's `.gitignore` file so that it will never
be committed by collaborators. This usage ensures that no sensitive passwords
or API keys will ever be in the version control history so there is less risk
of a security breach, and production values will never have to be shared with
all project collaborators.
Add your application configuration to a `.env` file in the root of your
project. **Make sure the `.env` file is added to your `.gitignore` so it is not
checked-in the code**
```shell
S3_BUCKET="dotenv"
SECRET_KEY="souper_seekret_key"
```
Now create a file named `.env.example` and check this into the project. This
should have the ENV variables you need to have set, but the values should
either be blank or filled with dummy data. The idea is to let people know what
variables are required, but not give them the sensitive production values.
```shell
S3_BUCKET="devbucket"
SECRET_KEY="abc123"
```
You can then load `.env` in your application with:
```php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
```
To suppress the exception that is thrown when there is no `.env` file, you can:
```php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();
```
Optionally you can pass in a filename as the second parameter, if you would
like to use something other than `.env`:
```php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__, 'myconfig');
$dotenv->load();
```
All of the defined variables are now available in the `$_ENV` and `$_SERVER`
super-globals.
```php
$s3_bucket = $_ENV['S3_BUCKET'];
$s3_bucket = $_SERVER['S3_BUCKET'];
```
### Putenv and Getenv
Using `getenv()` and `putenv()` is strongly discouraged due to the fact that
these functions are not thread safe, however it is still possible to instruct
PHP dotenv to use these functions. Instead of calling
`Dotenv::createImmutable`, one can call `Dotenv::createUnsafeImmutable`, which
will add the `PutenvAdapter` behind the scenes. Your environment variables will
now be available using the `getenv` method, as well as the super-globals:
```php
$s3_bucket = getenv('S3_BUCKET');
$s3_bucket = $_ENV['S3_BUCKET'];
$s3_bucket = $_SERVER['S3_BUCKET'];
```
### Nesting Variables
It's possible to nest an environment variable within another, useful to cut
down on repetition.
This is done by wrapping an existing environment variable in `${…}` e.g.
```shell
BASE_DIR="/var/webroot/project-root"
CACHE_DIR="${BASE_DIR}/cache"
TMP_DIR="${BASE_DIR}/tmp"
```
### Immutability and Repository Customization
Immutability refers to if Dotenv is allowed to overwrite existing environment
variables. If you want Dotenv to overwrite existing environment variables,
use `createMutable` instead of `createImmutable`:
```php
$dotenv = Dotenv\Dotenv::createMutable(__DIR__);
$dotenv->load();
```
Behind the scenes, this is instructing the "repository" to allow immutability
or not. By default, the repository is configured to allow overwriting existing
values by default, which is relevant if one is calling the "create" method
using the `RepositoryBuilder` to construct a more custom repository:
```php
$repository = Dotenv\Repository\RepositoryBuilder::createWithNoAdapters()
->addAdapter(Dotenv\Repository\Adapter\EnvConstAdapter::class)
->addWriter(Dotenv\Repository\Adapter\PutenvAdapter::class)
->immutable()
->make();
$dotenv = Dotenv\Dotenv::create($repository, __DIR__);
$dotenv->load();
```
The above example will write loaded values to `$_ENV` and `putenv`, but when
interpolating environment variables, we'll only read from `$_ENV`. Moreover, it
will never replace any variables already set before loading the file.
By means of another example, one can also specify a set of variables to be
allow listed. That is, only the variables in the allow list will be loaded:
```php
$repository = Dotenv\Repository\RepositoryBuilder::createWithDefaultAdapters()
->allowList(['FOO', 'BAR'])
->make();
$dotenv = Dotenv\Dotenv::create($repository, __DIR__);
$dotenv->load();
```
### Requiring Variables to be Set
PHP dotenv has built in validation functionality, including for enforcing the
presence of an environment variable. This is particularly useful to let people
know any explicit required variables that your app will not work without.
You can use a single string:
```php
$dotenv->required('DATABASE_DSN');
```
Or an array of strings:
```php
$dotenv->required(['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS']);
```
If any ENV vars are missing, Dotenv will throw a `RuntimeException` like this:
```
One or more environment variables failed assertions: DATABASE_DSN is missing
```
### Empty Variables
Beyond simply requiring a variable to be set, you might also need to ensure the
variable is not empty:
```php
$dotenv->required('DATABASE_DSN')->notEmpty();
```
If the environment variable is empty, you'd get an Exception:
```
One or more environment variables failed assertions: DATABASE_DSN is empty
```
### Integer Variables
You might also need to ensure that the variable is of an integer value. You may
do the following:
```php
$dotenv->required('FOO')->isInteger();
```
If the environment variable is not an integer, you'd get an Exception:
```
One or more environment variables failed assertions: FOO is not an integer.
```
One may only want to enforce validation rules when a variable is set. We
support this too:
```php
$dotenv->ifPresent('FOO')->isInteger();
```
### Boolean Variables
You may need to ensure a variable is in the form of a boolean, accepting
"true", "false", "On", "1", "Yes", "Off", "0" and "No". You may do the
following:
```php
$dotenv->required('FOO')->isBoolean();
```
If the environment variable is not a boolean, you'd get an Exception:
```
One or more environment variables failed assertions: FOO is not a boolean.
```
Similarly, one may write:
```php
$dotenv->ifPresent('FOO')->isBoolean();
```
### Allowed Values
It is also possible to define a set of values that your environment variable
should be. This is especially useful in situations where only a handful of
options or drivers are actually supported by your code:
```php
$dotenv->required('SESSION_STORE')->allowedValues(['Filesystem', 'Memcached']);
```
If the environment variable wasn't in this list of allowed values, you'd get a
similar Exception:
```
One or more environment variables failed assertions: SESSION_STORE is not an allowed value.
```
It is also possible to define a regex that your environment variable should be.
```php
$dotenv->required('FOO')->allowedRegexValues('([[:lower:]]{3})');
```
### Comments
You can comment your `.env` file using the `#` character. E.g.
```shell
# this is a comment
VAR="value" # comment
VAR=value # comment
```
### Parsing Without Loading
Sometimes you just wanna parse the file and resolve the nested environment variables, by giving us a string, and have an array returned back to you. While this is already possible, it is a little fiddly, so we have provided a direct way to do this:
```php
// ['FOO' => 'Bar', 'BAZ' => 'Hello Bar']
Dotenv\Dotenv::parse("FOO=Bar\nBAZ=\"Hello \${FOO}\"");
```
This is exactly the same as:
```php
Dotenv\Dotenv::createArrayBacked(__DIR__)->load();
```
only, instead of providing the directory to find the file, you have directly provided the file contents.
### Usage Notes
When a new developer clones your codebase, they will have an additional
one-time step to manually copy the `.env.example` file to `.env` and fill-in
their own values (or get any sensitive values from a project co-worker).
### Troubleshooting
In certain server setups (most commonly found in shared hosting), PHP might deactivate superglobals like `$_ENV` or `$_SERVER`. If these variables are not set, review the `variables_order` in the `php.ini` file. See [php.net/manual/en/ini.core.php#ini.variables-order](https://www.php.net/manual/en/ini.core.php#ini.variables-order).
## Security
If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. You may view our full security policy [here](https://github.com/vlucas/phpdotenv/security/policy).
## License
PHP dotenv is licensed under [The BSD 3-Clause License](LICENSE).
## For Enterprise
Available as part of the Tidelift Subscription
The maintainers of `vlucas/phpdotenv` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-vlucas-phpdotenv?utm_source=packagist-vlucas-phpdotenv&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
================================================
FILE: UPGRADING.md
================================================
# Upgrading Guide
## V5.5 to V5.6
Bumping the minimum required PHP version is not a breaking change, however it is notable. Since version 5.6.0, we now require PHP 7.2.5 or higher. Installation metrics show that for some time, PHP 7.1 has represented only around 0.1% of installs of V5.
Release notes for 5.6.0 are available [here](https://github.com/vlucas/phpdotenv/releases/tag/v5.6.0).
## V4 to V5
### Introduction
Version 5 bumps to PHP 7.1+, and adds some additional parameter typing. There have been some internal changes and refactorings too, but nothing that changes the overall feel and usage of the package. The Dotenv class itself is largely unchanged from V4.
Release notes for 5.0.0 are available [here](https://github.com/vlucas/phpdotenv/releases/tag/v5.0.0).
### Details
1. The `Dotenv\Dotenv::createImmutable` and `Dotenv\Dotenv::createMutable` methods no longer call will result in `getenv` and `putenv` being called. One should instead use `Dotenv\Dotenv::createUnsafeImmutable` and `Dotenv\Dotenv::createUnsafeMutable` methods, if one really needs these functions.
2. The `Dotenv\Dotenv` constructor has been modified to expect exactly 4 parameters: a store, a parser, a loader, and a repository. This likely won't affect many people, since it is more common to construct this class via the public static create methods. Those methods have not changed.
3. Scalar typehints have been added to the public interface.
4. The parser now returns a result type instead of raising an exception. This change is strictly internal, and most users won't notice a difference. The responsibility for raising an exception has simply been shifted up to the caller.
5. Adapters have been refactored again, with changes to the repositories. In particular, the repository builder has been tweaked. It now expects to be explicitly told if you want to use the default adapters or not, and expects individual readers and writers to be added, one by one. Similar changes have been applied to the store factory. Moreover, the `ApacheAdapter` has been changed so that it behaves much like the other adapters. The old behaviour can be simulated by composing it with the new `ReplacingWriter` (see below). We will no longer include this adapter in our default setup, so that people can enable exactly what they need. Finally, by default, we will no longer be using the `PutenvAdapter`. It can be added, as required.
6. Variable whitelisting has been replaced with allow listing, and the responsibility has moved from the loader to a new adapter `GuardedWriter`.
7. The parser has been moved to its own namespace and parses entire files now. This change is expected to have little impact when upgrading. The `Lines` class has also moved to the parser namespace.
8. The loader now only returns the variables that were actually loaded into the repository, and not all the variables from the file. Moreover, it now expects as input the result of running the new parser (an array of entries), rather than raw file content.
The changes listed in (4) mean that instead of:
```php
$repository = Dotenv\Repository\RepositoryBuilder::create()
->withReaders([
new Dotenv\Repository\Adapter\EnvConstAdapter(),
])
->withWriters([
new Dotenv\Repository\Adapter\EnvConstAdapter(),
new Dotenv\Repository\Adapter\PutenvAdapter(),
])
->make();
```
one would now write:
```php
$repository = Dotenv\Repository\RepositoryBuilder::createWithNoAdapters()
->addAdapter(Dotenv\Repository\Adapter\EnvConstAdapter::class)
->addWriter(Dotenv\Repository\Adapter\PutenvAdapter::class)
->make();
```
Instead of passing class names, one can also pass actual adapter instances. Note that it is not possible to directly construct any of the adapters. One has to go via their static `create` method which returns an optional. This is to strictly encapsulate the fact that not all adapters are capable of running on all systems, and so those that cannot be run, cannot be created. For example, the apache adapter can only be run within an apache web server context. Passing the class names as in the above example will handle this for you, by adding the adapter only if it can be created (the optional has a value set).
To add an apache environment variable writer that only writes to existing apache environment variables, as was the default in v4, one should do the following:
```php
$builder = Dotenv\Repository\RepositoryBuilder::createWithDefaultAdapters();
Dotenv\Repository\Adapter\ApacheAdapter::create()->map(function ($adapter) {
return new Dotenv\Repository\Adapter\ReplacingWriter($adapter, $adapter);
})->map([$builder, 'addWriter'])->getOrElse($builder);
$repository = $builder->make();
```
The use of optionals handles the case where the apache environment functions are not available (such as in a CLI environment).
## V4.0 to V4.1
### Introduction
Version 4.1 is a minor release, and as such, there are no breaking changes. There is, however a deprecation to be noted.
### Details
The `Dotenv\Dotenv` constructor now expects either an array of file paths as the third parameter, or an instance of `Dotenv\Store\StoreInterface`. Passing an array is deprecated, and will be removed in V5.
## V3 to V4
### Introduction
Version 4 sees some refactoring, and support for escaping dollars in values (https://github.com/vlucas/phpdotenv/pull/380). It is no longer possible to change immutability on the fly, and the `Loader` no longer is responsible for tracking immutability. It is now the responsibility of "repositories" to track this. One must explicitly decide if they want (im)mutability when constructing an instance of `Dotenv\Dotenv`.
Release notes for 4.0.0 are available [here](https://github.com/vlucas/phpdotenv/releases/tag/v4.0.0).
### Details
V4 has again changed the way you initialize the `Dotenv` class. If you want immutable loading of environment variables, then replace `Dotenv::create` with `Dotenv::createImmutable`, and if you want mutable loading, replace `Dotenv::create` with `Dotenv::createMutable` and `->overload()` with `->load()`. The `overload` method has been removed in favour of specifying mutability at object construction.
The behaviour when parsing single quoted strings has now changed, to mimic the behaviour of bash. It is no longer possible to escape characters in single quoted strings, and everything is treated literally. As soon as the first single quote character is read, after the initial one, then the variable is treated as ending immediately at that point. When parsing unquoted or double quoted strings, it is now possible to escape dollar signs, to forcefully avoid variable interpolation. Escaping dollars is not mandated, in the sense that if a dollar is present, and not following by variable interpolation syntax, this is allowed, and the dollar will be treated as a literal dollar. Finally, interpolation of variables is now performed right to left, instead of left to right, so it is possible to nest interpolations to allow using the value of a variable as the name of another for further interpolation.
The `getEnvironmentVariableNames` method is no longer available. This is because calls to `load()` (since v3.0.0) return an associative array of what was loaded, so `$dotenv->getEnvironmentVariableNames()` can be replaced with `array_keys($dotenv->load())`.
There have been various internal refactorings. Apart from what has already been mentioned, the only other changes likely to affect developers is:
1. The `Dotenv\Environment` namespace has been moved to `Dotenv\Repository`, the `Dotenv\Environment\Adapter\AdapterInterface` interface has been replaced by `Dotenv\Repository\Adapter\ReaderInterface` and `Dotenv\Repository\Adapter\WriterInterface`.
2. The `Dotenv\Environment\DotenvFactory` has been (roughly) replaced by `Dotenv\Repository\RepositoryBuilder`, and `Dotenv\Environment\FactoryInterface` has been deleted.
3. `Dotenv\Environment\AbstractVariables` has been replaced by `Dotenv\Repository\AbstractRepository`, `Dotenv\Environment\DotenvVariables` has been replaced by `Dotenv\Repository\AdapterRepository`, and `Dotenv\Environment\VariablesInterface` has been replaced by `Dotenv\Repository\RepositoryInterface`.
4. The `Dotenv\Loader` class has been moved to `Dotenv\Loader\Loader`, and now has a different public interface. It no longer expects any parameters at construction, and implements only the new interface `Dotenv\Loader\LoaderInterface`. Its responsibility has changed to purely taking raw env file content, and handing it off to the parser, dealing with variable interpolation, and sending off instructions to the repository to set variables. No longer can it be used as a way to read the environment by callers, and nor does it track immutability.
5. The `Dotenv\Parser` and `Dotenv\Lines` classes have moved to `Dotenv\Loader\Parser` and `Dotenv\Loader\Lines`, respectively. `Dotenv\Loader\Parser::parse` now return has either `null` or `Dotenv\Loader\Value` objects as values, instead of `string`s. This is to support the new variable interpolation and dollar escaping features.
6. The `Dotenv\Validator` constructor has changed from `__construct(array $variables, Loader $loader, $required = true)` to `__construct(RepositoryInterface $repository, array $variables, $required = true)`.
The example at the bottom of the below upgrading guide, in V4 now looks like:
```php
withReaders($adapters)
->withWriters($adapters)
->immutable()
->make();
Dotenv::create($repository, $path, null)->load();
```
Since v3.2.0, it was easily possible to read a file and process variable interpolations, without actually "loading" the variables. This is still possible in v4.0.0. Example code that does this is as follows:
```php
withReaders($adapters)
->withWriters($adapters)
->make();
$variables = (new Loader())->load($repository, $content);
```
Notice, that compared to v3, the loader no longer expects file paths in the constructor. Reading of the files is now managed by the `Dotenv\Dotenv` class. The loader is genuinely just loading the content into the repository.
Finally, we note that the minimum supported version of PHP has increased to 5.5.9, up from 5.4.0 in V3 and 5.3.9 in V2.
## V2 to V3
### Introduction
New in Version 3 is first-class support for multiline variables ([#301](https://github.com/vlucas/phpdotenv/pull/301)) and much more flexibility in terms of which parts of the environment we try to read and modify ([#300](https://github.com/vlucas/phpdotenv/pull/300)). Consequently, you will need to replace any occurrences of `new Dotenv(...)` with `Dotenv::create(...)`, since our new native constructor takes a `Loader` instance now, so that it can be truly customized if required. Finally, one should note that the loader will no longer be trimming values ([#302](https://github.com/vlucas/phpdotenv/pull/302)), moreover `Loader::load()` and its callers now return an associative array of the variables loaded with their values, rather than an array of raw lines from the environment file ([#306](https://github.com/vlucas/phpdotenv/pull/306)).
Release notes for 3.0.0 are available [here](https://github.com/vlucas/phpdotenv/releases/tag/v3.0.0).
### Details
V3 has changed the way you initialize the `Dotenv` class. Consequently, you will need to replace any occurrences of new Dotenv(...) with Dotenv::create(...), since our new native constructor takes a `Loader` instance now.
`Loader::load()` and its callers now return an associative array of the variables loaded with their values.
Value parsing has been modified in the following ways:
1. For unquoted strings, as soon as there's a hash, it's treated as a comment start.
2. We're being stricter about invalid escape sequences within quoted strings.
3. We're no longer trimming the parsed values of quoted strings.
4. Multiline quoted values are now permitted, and will be parsed by V3.
| input value | V2.5.2 | V2.6.1 | V3.3.1 |
|-|-|-|-|
| `foo#bar` | `foo#bar` | `foo#bar` | `foo` |
| `foo # bar` | `foo` | `foo` | `foo` |
| `"iiiiviiiixiiiiviiii\n"` | silent failure | `iiiviiiixiiiiviiii\n` | fails with invalid escape sequence exception |
| `"iiiiviiiixiiiiviiii\\n"` | `iiiiviiiixiiiiviiii\n` | `iiiiviiiixiiiiviiii\n` | `iiiiviiiixiiiiviiii\n` |
| `"foo\"bar"` | `foo"bar` | `foo"bar` | `foo"bar` |
| `" foo "` | `foo` with whitespace trimmed | `foo` with whitespace trimmed | `foo` with 2 spaces in front and one after |
In double quoted strings, double quotes and backslashes need escaping with a backslash, and in single quoted strings, single quote and backslashes need escaping with a backslash. In v2.5.2, forgetting an escape can lead to odd results due to the regex running out of stack, but this was fixed in 2.6 and 3.3, with 2.6 allowing you to continue after an unescaped backslash, but 3.3 not.
It's possible to use phpdotenv V3 in a threaded environment, instructing it to not call any functions that are not tread-safe:
```php
load();
```
Finally, we note that the minimum supported version of PHP has increased from 5.3.9 to 5.4.0.
================================================
FILE: composer.json
================================================
{
"name": "vlucas/phpdotenv",
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
"keywords": ["env", "dotenv", "environment"],
"license": "BSD-3-Clause",
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Vance Lucas",
"email": "vance@vancelucas.com",
"homepage": "https://github.com/vlucas"
}
],
"require": {
"php": "^7.2.5 || ^8.0",
"ext-pcre": "*",
"graham-campbell/result-type": "^1.1.4",
"phpoption/phpoption": "^1.9.5",
"symfony/polyfill-ctype": "^1.26",
"symfony/polyfill-mbstring": "^1.26",
"symfony/polyfill-php80": "^1.26"
},
"require-dev": {
"ext-filter": "*",
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit":"^8.5.34 || ^9.6.13 || ^10.4.2"
},
"autoload": {
"psr-4": {
"Dotenv\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Dotenv\\Tests\\": "tests/Dotenv/"
}
},
"suggest": {
"ext-filter": "Required to use the boolean validator."
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
},
"preferred-install": "dist"
},
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "5.6-dev"
}
}
}
================================================
FILE: phpstan-baseline.neon
================================================
parameters:
ignoreErrors:
-
message: '#^PHPDoc tag @var with type PhpOption\\Option\ is not subtype of type PhpOption\\Option\\.$#'
identifier: varTag.type
count: 1
path: src/Parser/Entry.php
-
message: '#^Anonymous function should return GrahamCampbell\\ResultType\\Result\ but returns GrahamCampbell\\ResultType\\Result\\.$#'
identifier: return.type
count: 1
path: src/Parser/EntryParser.php
-
message: '#^Method Dotenv\\Parser\\EntryParser\:\:parse\(\) should return GrahamCampbell\\ResultType\\Result\ but returns GrahamCampbell\\ResultType\\Result\\.$#'
identifier: return.type
count: 1
path: src/Parser/EntryParser.php
-
message: '#^PHPDoc tag @var with type GrahamCampbell\\ResultType\\Result\ is not subtype of type GrahamCampbell\\ResultType\\Result\\|GrahamCampbell\\ResultType\\Result\\.$#'
identifier: varTag.type
count: 1
path: src/Parser/EntryParser.php
-
message: '#^Parameter \#2 \$callback of function array_reduce expects callable\(GrahamCampbell\\ResultType\\Result\\|GrahamCampbell\\ResultType\\Result\, string\)\: \(GrahamCampbell\\ResultType\\Result\\|GrahamCampbell\\ResultType\\Result\\), Closure\(GrahamCampbell\\ResultType\\Result, string\)\: GrahamCampbell\\ResultType\\Result\ given\.$#'
identifier: argument.type
count: 1
path: src/Parser/EntryParser.php
-
message: '#^Only booleans are allowed in a negated boolean, int\|false given\.$#'
identifier: booleanNot.exprNotBoolean
count: 1
path: src/Parser/Lexer.php
-
message: '#^Parameter \#1 \$pattern of function preg_match expects string, mixed given\.$#'
identifier: argument.type
count: 1
path: src/Parser/Lexer.php
-
message: '#^PHPDoc tag @var with type PhpOption\\Option\ is not subtype of type PhpOption\\Some\\.$#'
identifier: varTag.type
count: 1
path: src/Repository/Adapter/ApacheAdapter.php
-
message: '#^PHPDoc tag @var with type PhpOption\\Option\ is not subtype of type PhpOption\\Option\\.$#'
identifier: varTag.type
count: 1
path: src/Repository/Adapter/ApacheAdapter.php
-
message: '#^PHPDoc tag @var with type PhpOption\\Option\ is not subtype of type PhpOption\\Some\\.$#'
identifier: varTag.type
count: 1
path: src/Repository/Adapter/ArrayAdapter.php
-
message: '#^Cannot cast mixed to string\.$#'
identifier: cast.string
count: 1
path: src/Repository/Adapter/EnvConstAdapter.php
-
message: '#^PHPDoc tag @var with type PhpOption\\Option\ is not subtype of type PhpOption\\Some\\.$#'
identifier: varTag.type
count: 1
path: src/Repository/Adapter/EnvConstAdapter.php
-
message: '#^PHPDoc tag @var with type PhpOption\\Option\ is not subtype of type PhpOption\\Some\\.$#'
identifier: varTag.type
count: 1
path: src/Repository/Adapter/PutenvAdapter.php
-
message: '#^PHPDoc tag @var with type PhpOption\\Option\ is not subtype of type PhpOption\\Option\\.$#'
identifier: varTag.type
count: 1
path: src/Repository/Adapter/PutenvAdapter.php
-
message: '#^Cannot cast mixed to string\.$#'
identifier: cast.string
count: 1
path: src/Repository/Adapter/ServerConstAdapter.php
-
message: '#^PHPDoc tag @var with type PhpOption\\Option\ is not subtype of type PhpOption\\Some\\.$#'
identifier: varTag.type
count: 1
path: src/Repository/Adapter/ServerConstAdapter.php
-
message: '#^Parameter \#1 \$callable of method PhpOption\\Some\\:\:flatMap\(\) expects callable\(Dotenv\\Repository\\Adapter\\AdapterInterface\|string\)\: PhpOption\\Option\, Closure\(mixed\)\: mixed given\.$#'
identifier: argument.type
count: 1
path: src/Repository/RepositoryBuilder.php
-
message: '#^Parameter \#1 \$callable of method PhpOption\\Some\\:\:flatMap\(\) expects callable\(Dotenv\\Repository\\Adapter\\ReaderInterface\|string\)\: PhpOption\\Option\, Closure\(mixed\)\: mixed given\.$#'
identifier: argument.type
count: 1
path: src/Repository/RepositoryBuilder.php
-
message: '#^Parameter \#1 \$callable of method PhpOption\\Some\\:\:flatMap\(\) expects callable\(Dotenv\\Repository\\Adapter\\WriterInterface\|string\)\: PhpOption\\Option\, Closure\(mixed\)\: mixed given\.$#'
identifier: argument.type
count: 1
path: src/Repository/RepositoryBuilder.php
-
message: '#^Parameter \#1 \$readers of class Dotenv\\Repository\\RepositoryBuilder constructor expects array\, array\ given\.$#'
identifier: argument.type
count: 2
path: src/Repository/RepositoryBuilder.php
-
message: '#^Parameter \#2 \$writers of class Dotenv\\Repository\\RepositoryBuilder constructor expects array\, array\ given\.$#'
identifier: argument.type
count: 2
path: src/Repository/RepositoryBuilder.php
-
message: '#^PHPDoc tag @var with type PhpOption\\Option\ is not subtype of type PhpOption\\Option\\.$#'
identifier: varTag.type
count: 1
path: src/Store/File/Reader.php
-
message: '#^Method Dotenv\\Util\\Regex\:\:occurrences\(\) should return GrahamCampbell\\ResultType\\Result\ but returns GrahamCampbell\\ResultType\\Result\, string\>\.$#'
identifier: return.type
count: 1
path: src/Util/Regex.php
-
message: '#^Loose comparison via "\=\=" is not allowed\.$#'
identifier: equal.notAllowed
count: 1
path: src/Util/Str.php
-
message: '#^PHPDoc tag @var with type GrahamCampbell\\ResultType\\Result\ is not subtype of type GrahamCampbell\\ResultType\\Result\\.$#'
identifier: varTag.type
count: 2
path: src/Util/Str.php
-
message: '#^PHPDoc tag @var with type PhpOption\\Option\ is not subtype of type PhpOption\\Option\\|false\>\.$#'
identifier: varTag.type
count: 1
path: src/Util/Str.php
================================================
FILE: phpstan.neon.dist
================================================
includes:
- phpstan-baseline.neon
parameters:
level: max
paths:
- src
================================================
FILE: phpunit.xml.dist
================================================
./tests./src
================================================
FILE: src/Dotenv.php
================================================
store = $store;
$this->parser = $parser;
$this->loader = $loader;
$this->repository = $repository;
}
/**
* Create a new dotenv instance.
*
* @param \Dotenv\Repository\RepositoryInterface $repository
* @param string|string[] $paths
* @param string|string[]|null $names
* @param bool $shortCircuit
* @param string|null $fileEncoding
*
* @return \Dotenv\Dotenv
*/
public static function create(RepositoryInterface $repository, $paths, $names = null, bool $shortCircuit = true, ?string $fileEncoding = null)
{
$builder = $names === null ? StoreBuilder::createWithDefaultName() : StoreBuilder::createWithNoNames();
foreach ((array) $paths as $path) {
$builder = $builder->addPath($path);
}
foreach ((array) $names as $name) {
$builder = $builder->addName($name);
}
if ($shortCircuit) {
$builder = $builder->shortCircuit();
}
return new self($builder->fileEncoding($fileEncoding)->make(), new Parser(), new Loader(), $repository);
}
/**
* Create a new mutable dotenv instance with default repository.
*
* @param string|string[] $paths
* @param string|string[]|null $names
* @param bool $shortCircuit
* @param string|null $fileEncoding
*
* @return \Dotenv\Dotenv
*/
public static function createMutable($paths, $names = null, bool $shortCircuit = true, ?string $fileEncoding = null)
{
$repository = RepositoryBuilder::createWithDefaultAdapters()->make();
return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding);
}
/**
* Create a new mutable dotenv instance with default repository with the putenv adapter.
*
* @param string|string[] $paths
* @param string|string[]|null $names
* @param bool $shortCircuit
* @param string|null $fileEncoding
*
* @return \Dotenv\Dotenv
*/
public static function createUnsafeMutable($paths, $names = null, bool $shortCircuit = true, ?string $fileEncoding = null)
{
$repository = RepositoryBuilder::createWithDefaultAdapters()
->addAdapter(PutenvAdapter::class)
->make();
return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding);
}
/**
* Create a new immutable dotenv instance with default repository.
*
* @param string|string[] $paths
* @param string|string[]|null $names
* @param bool $shortCircuit
* @param string|null $fileEncoding
*
* @return \Dotenv\Dotenv
*/
public static function createImmutable($paths, $names = null, bool $shortCircuit = true, ?string $fileEncoding = null)
{
$repository = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make();
return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding);
}
/**
* Create a new immutable dotenv instance with default repository with the putenv adapter.
*
* @param string|string[] $paths
* @param string|string[]|null $names
* @param bool $shortCircuit
* @param string|null $fileEncoding
*
* @return \Dotenv\Dotenv
*/
public static function createUnsafeImmutable($paths, $names = null, bool $shortCircuit = true, ?string $fileEncoding = null)
{
$repository = RepositoryBuilder::createWithDefaultAdapters()
->addAdapter(PutenvAdapter::class)
->immutable()
->make();
return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding);
}
/**
* Create a new dotenv instance with an array backed repository.
*
* @param string|string[] $paths
* @param string|string[]|null $names
* @param bool $shortCircuit
* @param string|null $fileEncoding
*
* @return \Dotenv\Dotenv
*/
public static function createArrayBacked($paths, $names = null, bool $shortCircuit = true, ?string $fileEncoding = null)
{
$repository = RepositoryBuilder::createWithNoAdapters()->addAdapter(ArrayAdapter::class)->make();
return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding);
}
/**
* Parse the given content and resolve nested variables.
*
* This method behaves just like load(), only without mutating your actual
* environment. We do this by using an array backed repository.
*
* @param string $content
*
* @throws \Dotenv\Exception\InvalidFileException
*
* @return array
*/
public static function parse(string $content)
{
$repository = RepositoryBuilder::createWithNoAdapters()->addAdapter(ArrayAdapter::class)->make();
$phpdotenv = new self(new StringStore($content), new Parser(), new Loader(), $repository);
return $phpdotenv->load();
}
/**
* Read and load environment file(s).
*
* @throws \Dotenv\Exception\InvalidPathException|\Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidFileException
*
* @return array
*/
public function load()
{
$entries = $this->parser->parse($this->store->read());
return $this->loader->load($this->repository, $entries);
}
/**
* Read and load environment file(s), silently failing if no files can be read.
*
* @throws \Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidFileException
*
* @return array
*/
public function safeLoad()
{
try {
return $this->load();
} catch (InvalidPathException $e) {
// suppressing exception
return [];
}
}
/**
* Required ensures that the specified variables exist, and returns a new validator object.
*
* @param string|string[] $variables
*
* @return \Dotenv\Validator
*/
public function required($variables)
{
return (new Validator($this->repository, (array) $variables))->required();
}
/**
* Returns a new validator object that won't check if the specified variables exist.
*
* @param string|string[] $variables
*
* @return \Dotenv\Validator
*/
public function ifPresent($variables)
{
return new Validator($this->repository, (array) $variables);
}
}
================================================
FILE: src/Exception/ExceptionInterface.php
================================================
*/
public function load(RepositoryInterface $repository, array $entries)
{
/** @var array */
return \array_reduce($entries, static function (array $vars, Entry $entry) use ($repository) {
$name = $entry->getName();
$value = $entry->getValue()->map(static function (Value $value) use ($repository) {
return Resolver::resolve($repository, $value);
});
if ($value->isDefined()) {
$inner = $value->get();
if ($repository->set($name, $inner)) {
return \array_merge($vars, [$name => $inner]);
}
} else {
if ($repository->clear($name)) {
return \array_merge($vars, [$name => null]);
}
}
return $vars;
}, []);
}
}
================================================
FILE: src/Loader/LoaderInterface.php
================================================
*/
public function load(RepositoryInterface $repository, array $entries);
}
================================================
FILE: src/Loader/Resolver.php
================================================
getVars(), static function (string $s, int $i) use ($repository) {
return Str::substr($s, 0, $i).self::resolveVariable($repository, Str::substr($s, $i));
}, $value->getChars());
}
/**
* Resolve a single nested variable.
*
* @param \Dotenv\Repository\RepositoryInterface $repository
* @param string $str
*
* @return string
*/
private static function resolveVariable(RepositoryInterface $repository, string $str)
{
return Regex::replaceCallback(
'/\A\${([a-zA-Z0-9_.]+)}/',
static function (array $matches) use ($repository) {
/** @var string */
return Option::fromValue($repository->get($matches[1]))->getOrElse($matches[0]);
},
$str,
1
)->success()->getOrElse($str);
}
}
================================================
FILE: src/Parser/Entry.php
================================================
name = $name;
$this->value = $value;
}
/**
* Get the entry name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Get the entry value.
*
* @return \PhpOption\Option<\Dotenv\Parser\Value>
*/
public function getValue()
{
/** @var \PhpOption\Option<\Dotenv\Parser\Value> */
return Option::fromValue($this->value);
}
}
================================================
FILE: src/Parser/EntryParser.php
================================================
*/
public static function parse(string $entry)
{
return self::splitStringIntoParts($entry)->flatMap(static function (array $parts) {
[$name, $value] = $parts;
return self::parseName($name)->flatMap(static function (string $name) use ($value) {
/** @var Result */
$parsedValue = $value === null ? Success::create(null) : self::parseValue($value);
return $parsedValue->map(static function (?Value $value) use ($name) {
return new Entry($name, $value);
});
});
});
}
/**
* Split the compound string into parts.
*
* @param string $line
*
* @return \GrahamCampbell\ResultType\Result
*/
private static function splitStringIntoParts(string $line)
{
/** @var array{string, string|null} */
$result = Str::pos($line, '=')->map(static function () use ($line) {
return \array_map('trim', \explode('=', $line, 2));
})->getOrElse([$line, null]);
if ($result[0] === '') {
/** @var \GrahamCampbell\ResultType\Result */
return Error::create(self::getErrorMessage('an unexpected equals', $line));
}
/** @var \GrahamCampbell\ResultType\Result */
return Success::create($result);
}
/**
* Parse the given variable name.
*
* That is, strip the optional quotes and leading "export" from the
* variable name. We wrap the answer in a result type.
*
* @param string $name
*
* @return \GrahamCampbell\ResultType\Result
*/
private static function parseName(string $name)
{
if (Str::len($name) > 8 && Str::substr($name, 0, 6) === 'export' && \ctype_space(Str::substr($name, 6, 1))) {
$name = \ltrim(Str::substr($name, 6));
}
if (self::isQuotedName($name)) {
$name = Str::substr($name, 1, -1);
}
if (!self::isValidName($name)) {
/** @var \GrahamCampbell\ResultType\Result */
return Error::create(self::getErrorMessage('an invalid name', $name));
}
/** @var \GrahamCampbell\ResultType\Result */
return Success::create($name);
}
/**
* Is the given variable name quoted?
*
* @param string $name
*
* @return bool
*/
private static function isQuotedName(string $name)
{
if (Str::len($name) < 3) {
return false;
}
$first = Str::substr($name, 0, 1);
$last = Str::substr($name, -1, 1);
return ($first === '"' && $last === '"') || ($first === '\'' && $last === '\'');
}
/**
* Is the given variable name valid?
*
* @param string $name
*
* @return bool
*/
private static function isValidName(string $name)
{
return Regex::matches('~(*UTF8)\A[\p{Ll}\p{Lu}\p{M}\p{N}_.]+\z~', $name)->success()->getOrElse(false);
}
/**
* Parse the given variable value.
*
* This has the effect of stripping quotes and comments, dealing with
* special characters, and locating nested variables, but not resolving
* them. Formally, we run a finite state automaton with an output tape: a
* transducer. We wrap the answer in a result type.
*
* @param string $value
*
* @return \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value, string>
*/
private static function parseValue(string $value)
{
if (\trim($value) === '') {
/** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value, string> */
return Success::create(Value::blank());
}
return \array_reduce(\iterator_to_array(Lexer::lex($value)), static function (Result $data, string $token) {
return $data->flatMap(static function (array $data) use ($token) {
return self::processToken($data[1], $token)->map(static function (array $val) use ($data) {
return [$data[0]->append($val[0], $val[1]), $val[2]];
});
});
}, Success::create([Value::blank(), self::INITIAL_STATE]))->flatMap(static function (array $result) {
if (in_array($result[1], self::REJECT_STATES, true)) {
/** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value, string> */
return Error::create('a missing closing quote');
}
/** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value, string> */
return Success::create($result[0]);
})->mapError(static function (string $err) use ($value) {
return self::getErrorMessage($err, $value);
});
}
/**
* Process the given token.
*
* @param int $state
* @param string $token
*
* @return \GrahamCampbell\ResultType\Result
*/
private static function processToken(int $state, string $token)
{
switch ($state) {
case self::INITIAL_STATE:
if ($token === '\'') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create(['', false, self::SINGLE_QUOTED_STATE]);
} elseif ($token === '"') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create(['', false, self::DOUBLE_QUOTED_STATE]);
} elseif ($token === '#') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create(['', false, self::COMMENT_STATE]);
} elseif ($token === '$') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create([$token, true, self::UNQUOTED_STATE]);
} else {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create([$token, false, self::UNQUOTED_STATE]);
}
case self::UNQUOTED_STATE:
if ($token === '#') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create(['', false, self::COMMENT_STATE]);
} elseif (\ctype_space($token)) {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create(['', false, self::WHITESPACE_STATE]);
} elseif ($token === '$') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create([$token, true, self::UNQUOTED_STATE]);
} else {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create([$token, false, self::UNQUOTED_STATE]);
}
case self::SINGLE_QUOTED_STATE:
if ($token === '\'') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create(['', false, self::WHITESPACE_STATE]);
} else {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create([$token, false, self::SINGLE_QUOTED_STATE]);
}
case self::DOUBLE_QUOTED_STATE:
if ($token === '"') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create(['', false, self::WHITESPACE_STATE]);
} elseif ($token === '\\') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create(['', false, self::ESCAPE_SEQUENCE_STATE]);
} elseif ($token === '$') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create([$token, true, self::DOUBLE_QUOTED_STATE]);
} else {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]);
}
case self::ESCAPE_SEQUENCE_STATE:
if ($token === '"' || $token === '\\') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]);
} elseif ($token === '$') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]);
} else {
$first = Str::substr($token, 0, 1);
if (\in_array($first, ['f', 'n', 'r', 't', 'v'], true)) {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create([\stripcslashes('\\'.$first).Str::substr($token, 1), false, self::DOUBLE_QUOTED_STATE]);
} else {
/** @var \GrahamCampbell\ResultType\Result */
return Error::create('an unexpected escape sequence');
}
}
case self::WHITESPACE_STATE:
if ($token === '#') {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create(['', false, self::COMMENT_STATE]);
} elseif (!\ctype_space($token)) {
/** @var \GrahamCampbell\ResultType\Result */
return Error::create('unexpected whitespace');
} else {
/** @var \GrahamCampbell\ResultType\Result */
return Success::create(['', false, self::WHITESPACE_STATE]);
}
case self::COMMENT_STATE:
/** @var \GrahamCampbell\ResultType\Result */
return Success::create(['', false, self::COMMENT_STATE]);
default:
throw new \Error('Parser entered invalid state.');
}
}
/**
* Generate a friendly error message.
*
* @param string $cause
* @param string $subject
*
* @return string
*/
private static function getErrorMessage(string $cause, string $subject)
{
return \sprintf(
'Encountered %s at [%s].',
$cause,
\strtok($subject, "\n")
);
}
}
================================================
FILE: src/Parser/Lexer.php
================================================
*/
public static function lex(string $content)
{
static $regex;
if ($regex === null) {
$regex = '(('.\implode(')|(', self::PATTERNS).'))A';
}
$offset = 0;
while (isset($content[$offset])) {
if (!\preg_match($regex, $content, $matches, 0, $offset)) {
throw new \Error(\sprintf('Lexer encountered unexpected character [%s].', $content[$offset]));
}
$offset += \strlen($matches[0]);
yield $matches[0];
}
}
}
================================================
FILE: src/Parser/Lines.php
================================================
map(static function () use ($line) {
return self::looksLikeMultilineStop($line, true) === false;
})->getOrElse(false);
}
/**
* Determine if the given line can be the start of a multiline variable.
*
* @param string $line
* @param bool $started
*
* @return bool
*/
private static function looksLikeMultilineStop(string $line, bool $started)
{
if ($line === '"') {
return true;
}
return Regex::occurrences('/(?=([^\\\\]"))/', \str_replace('\\\\', '', $line))->map(static function (int $count) use ($started) {
return $started ? $count > 1 : $count >= 1;
})->success()->getOrElse(false);
}
/**
* Determine if the line in the file is a comment or whitespace.
*
* @param string $line
*
* @return bool
*/
private static function isCommentOrWhitespace(string $line)
{
$line = \trim($line);
return $line === '' || (isset($line[0]) && $line[0] === '#');
}
}
================================================
FILE: src/Parser/Parser.php
================================================
mapError(static function () {
return 'Could not split into separate lines.';
})->flatMap(static function (array $lines) {
return self::process(Lines::process($lines));
})->mapError(static function (string $error) {
throw new InvalidFileException(\sprintf('Failed to parse dotenv file. %s', $error));
})->success()->get();
}
/**
* Convert the raw entries into proper entries.
*
* @param string[] $entries
*
* @return \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry[], string>
*/
private static function process(array $entries)
{
/** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry[], string> */
return \array_reduce($entries, static function (Result $result, string $raw) {
return $result->flatMap(static function (array $entries) use ($raw) {
return EntryParser::parse($raw)->map(static function (Entry $entry) use ($entries) {
/** @var \Dotenv\Parser\Entry[] */
return \array_merge($entries, [$entry]);
});
});
}, Success::create([]));
}
}
================================================
FILE: src/Parser/ParserInterface.php
================================================
chars = $chars;
$this->vars = $vars;
}
/**
* Create an empty value instance.
*
* @return \Dotenv\Parser\Value
*/
public static function blank()
{
return new self('', []);
}
/**
* Create a new value instance, appending the characters.
*
* @param string $chars
* @param bool $var
*
* @return \Dotenv\Parser\Value
*/
public function append(string $chars, bool $var)
{
return new self(
$this->chars.$chars,
$var ? \array_merge($this->vars, [Str::len($this->chars)]) : $this->vars
);
}
/**
* Get the string representation of the parsed value.
*
* @return string
*/
public function getChars()
{
return $this->chars;
}
/**
* Get the locations of the variables in the value.
*
* @return int[]
*/
public function getVars()
{
$vars = $this->vars;
\rsort($vars);
return $vars;
}
}
================================================
FILE: src/Repository/Adapter/AdapterInterface.php
================================================
*/
public static function create();
}
================================================
FILE: src/Repository/Adapter/ApacheAdapter.php
================================================
*/
public static function create()
{
if (self::isSupported()) {
/** @var \PhpOption\Option */
return Some::create(new self());
}
return None::create();
}
/**
* Determines if the adapter is supported.
*
* This happens if PHP is running as an Apache module.
*
* @return bool
*/
private static function isSupported()
{
return \function_exists('apache_getenv') && \function_exists('apache_setenv');
}
/**
* Read an environment variable, if it exists.
*
* @param non-empty-string $name
*
* @return \PhpOption\Option
*/
public function read(string $name)
{
/** @var \PhpOption\Option */
return Option::fromValue(apache_getenv($name))->filter(static function ($value) {
return \is_string($value) && $value !== '';
});
}
/**
* Write to an environment variable, if possible.
*
* @param non-empty-string $name
* @param string $value
*
* @return bool
*/
public function write(string $name, string $value)
{
return apache_setenv($name, $value);
}
/**
* Delete an environment variable, if possible.
*
* @param non-empty-string $name
*
* @return bool
*/
public function delete(string $name)
{
return apache_setenv($name, '');
}
}
================================================
FILE: src/Repository/Adapter/ArrayAdapter.php
================================================
*/
private $variables;
/**
* Create a new array adapter instance.
*
* @return void
*/
private function __construct()
{
$this->variables = [];
}
/**
* Create a new instance of the adapter, if it is available.
*
* @return \PhpOption\Option<\Dotenv\Repository\Adapter\AdapterInterface>
*/
public static function create()
{
/** @var \PhpOption\Option */
return Some::create(new self());
}
/**
* Read an environment variable, if it exists.
*
* @param non-empty-string $name
*
* @return \PhpOption\Option
*/
public function read(string $name)
{
return Option::fromArraysValue($this->variables, $name);
}
/**
* Write to an environment variable, if possible.
*
* @param non-empty-string $name
* @param string $value
*
* @return bool
*/
public function write(string $name, string $value)
{
$this->variables[$name] = $value;
return true;
}
/**
* Delete an environment variable, if possible.
*
* @param non-empty-string $name
*
* @return bool
*/
public function delete(string $name)
{
unset($this->variables[$name]);
return true;
}
}
================================================
FILE: src/Repository/Adapter/EnvConstAdapter.php
================================================
*/
public static function create()
{
/** @var \PhpOption\Option */
return Some::create(new self());
}
/**
* Read an environment variable, if it exists.
*
* @param non-empty-string $name
*
* @return \PhpOption\Option
*/
public function read(string $name)
{
/** @var \PhpOption\Option */
return Option::fromArraysValue($_ENV, $name)
->filter(static function ($value) {
return \is_scalar($value);
})
->map(static function ($value) {
if ($value === false) {
return 'false';
}
if ($value === true) {
return 'true';
}
return (string) $value;
});
}
/**
* Write to an environment variable, if possible.
*
* @param non-empty-string $name
* @param string $value
*
* @return bool
*/
public function write(string $name, string $value)
{
$_ENV[$name] = $value;
return true;
}
/**
* Delete an environment variable, if possible.
*
* @param non-empty-string $name
*
* @return bool
*/
public function delete(string $name)
{
unset($_ENV[$name]);
return true;
}
}
================================================
FILE: src/Repository/Adapter/GuardedWriter.php
================================================
writer = $writer;
$this->allowList = $allowList;
}
/**
* Write to an environment variable, if possible.
*
* @param non-empty-string $name
* @param string $value
*
* @return bool
*/
public function write(string $name, string $value)
{
// Don't set non-allowed variables
if (!$this->isAllowed($name)) {
return false;
}
// Set the value on the inner writer
return $this->writer->write($name, $value);
}
/**
* Delete an environment variable, if possible.
*
* @param non-empty-string $name
*
* @return bool
*/
public function delete(string $name)
{
// Don't clear non-allowed variables
if (!$this->isAllowed($name)) {
return false;
}
// Set the value on the inner writer
return $this->writer->delete($name);
}
/**
* Determine if the given variable is allowed.
*
* @param non-empty-string $name
*
* @return bool
*/
private function isAllowed(string $name)
{
return \in_array($name, $this->allowList, true);
}
}
================================================
FILE: src/Repository/Adapter/ImmutableWriter.php
================================================
*/
private $loaded;
/**
* Create a new immutable writer instance.
*
* @param \Dotenv\Repository\Adapter\WriterInterface $writer
* @param \Dotenv\Repository\Adapter\ReaderInterface $reader
*
* @return void
*/
public function __construct(WriterInterface $writer, ReaderInterface $reader)
{
$this->writer = $writer;
$this->reader = $reader;
$this->loaded = [];
}
/**
* Write to an environment variable, if possible.
*
* @param non-empty-string $name
* @param string $value
*
* @return bool
*/
public function write(string $name, string $value)
{
// Don't overwrite existing environment variables
// Ruby's dotenv does this with `ENV[key] ||= value`
if ($this->isExternallyDefined($name)) {
return false;
}
// Set the value on the inner writer
if (!$this->writer->write($name, $value)) {
return false;
}
// Record that we have loaded the variable
$this->loaded[$name] = '';
return true;
}
/**
* Delete an environment variable, if possible.
*
* @param non-empty-string $name
*
* @return bool
*/
public function delete(string $name)
{
// Don't clear existing environment variables
if ($this->isExternallyDefined($name)) {
return false;
}
// Clear the value on the inner writer
if (!$this->writer->delete($name)) {
return false;
}
// Leave the variable as fair game
unset($this->loaded[$name]);
return true;
}
/**
* Determine if the given variable is externally defined.
*
* That is, is it an "existing" variable.
*
* @param non-empty-string $name
*
* @return bool
*/
private function isExternallyDefined(string $name)
{
return $this->reader->read($name)->isDefined() && !isset($this->loaded[$name]);
}
}
================================================
FILE: src/Repository/Adapter/MultiReader.php
================================================
readers = $readers;
}
/**
* Read an environment variable, if it exists.
*
* @param non-empty-string $name
*
* @return \PhpOption\Option
*/
public function read(string $name)
{
foreach ($this->readers as $reader) {
$result = $reader->read($name);
if ($result->isDefined()) {
return $result;
}
}
return None::create();
}
}
================================================
FILE: src/Repository/Adapter/MultiWriter.php
================================================
writers = $writers;
}
/**
* Write to an environment variable, if possible.
*
* @param non-empty-string $name
* @param string $value
*
* @return bool
*/
public function write(string $name, string $value)
{
foreach ($this->writers as $writers) {
if (!$writers->write($name, $value)) {
return false;
}
}
return true;
}
/**
* Delete an environment variable, if possible.
*
* @param non-empty-string $name
*
* @return bool
*/
public function delete(string $name)
{
foreach ($this->writers as $writers) {
if (!$writers->delete($name)) {
return false;
}
}
return true;
}
}
================================================
FILE: src/Repository/Adapter/PutenvAdapter.php
================================================
*/
public static function create()
{
if (self::isSupported()) {
/** @var \PhpOption\Option */
return Some::create(new self());
}
return None::create();
}
/**
* Determines if the adapter is supported.
*
* @return bool
*/
private static function isSupported()
{
return \function_exists('getenv') && \function_exists('putenv');
}
/**
* Read an environment variable, if it exists.
*
* @param non-empty-string $name
*
* @return \PhpOption\Option
*/
public function read(string $name)
{
/** @var \PhpOption\Option */
return Option::fromValue(\getenv($name), false)->filter(static function ($value) {
return \is_string($value);
});
}
/**
* Write to an environment variable, if possible.
*
* @param non-empty-string $name
* @param string $value
*
* @return bool
*/
public function write(string $name, string $value)
{
\putenv("$name=$value");
return true;
}
/**
* Delete an environment variable, if possible.
*
* @param non-empty-string $name
*
* @return bool
*/
public function delete(string $name)
{
\putenv($name);
return true;
}
}
================================================
FILE: src/Repository/Adapter/ReaderInterface.php
================================================
*/
public function read(string $name);
}
================================================
FILE: src/Repository/Adapter/ReplacingWriter.php
================================================
*/
private $seen;
/**
* Create a new replacement writer instance.
*
* @param \Dotenv\Repository\Adapter\WriterInterface $writer
* @param \Dotenv\Repository\Adapter\ReaderInterface $reader
*
* @return void
*/
public function __construct(WriterInterface $writer, ReaderInterface $reader)
{
$this->writer = $writer;
$this->reader = $reader;
$this->seen = [];
}
/**
* Write to an environment variable, if possible.
*
* @param non-empty-string $name
* @param string $value
*
* @return bool
*/
public function write(string $name, string $value)
{
if ($this->exists($name)) {
return $this->writer->write($name, $value);
}
// succeed if nothing to do
return true;
}
/**
* Delete an environment variable, if possible.
*
* @param non-empty-string $name
*
* @return bool
*/
public function delete(string $name)
{
if ($this->exists($name)) {
return $this->writer->delete($name);
}
// succeed if nothing to do
return true;
}
/**
* Does the given environment variable exist.
*
* Returns true if it currently exists, or existed at any point in the past
* that we are aware of.
*
* @param non-empty-string $name
*
* @return bool
*/
private function exists(string $name)
{
if (isset($this->seen[$name])) {
return true;
}
if ($this->reader->read($name)->isDefined()) {
$this->seen[$name] = '';
return true;
}
return false;
}
}
================================================
FILE: src/Repository/Adapter/ServerConstAdapter.php
================================================
*/
public static function create()
{
/** @var \PhpOption\Option */
return Some::create(new self());
}
/**
* Read an environment variable, if it exists.
*
* @param non-empty-string $name
*
* @return \PhpOption\Option
*/
public function read(string $name)
{
/** @var \PhpOption\Option */
return Option::fromArraysValue($_SERVER, $name)
->filter(static function ($value) {
return \is_scalar($value);
})
->map(static function ($value) {
if ($value === false) {
return 'false';
}
if ($value === true) {
return 'true';
}
return (string) $value;
});
}
/**
* Write to an environment variable, if possible.
*
* @param non-empty-string $name
* @param string $value
*
* @return bool
*/
public function write(string $name, string $value)
{
$_SERVER[$name] = $value;
return true;
}
/**
* Delete an environment variable, if possible.
*
* @param non-empty-string $name
*
* @return bool
*/
public function delete(string $name)
{
unset($_SERVER[$name]);
return true;
}
}
================================================
FILE: src/Repository/Adapter/WriterInterface.php
================================================
reader = $reader;
$this->writer = $writer;
}
/**
* Determine if the given environment variable is defined.
*
* @param string $name
*
* @return bool
*/
public function has(string $name)
{
return '' !== $name && $this->reader->read($name)->isDefined();
}
/**
* Get an environment variable.
*
* @param string $name
*
* @throws \InvalidArgumentException
*
* @return string|null
*/
public function get(string $name)
{
if ('' === $name) {
throw new InvalidArgumentException('Expected name to be a non-empty string.');
}
return $this->reader->read($name)->getOrElse(null);
}
/**
* Set an environment variable.
*
* @param string $name
* @param string $value
*
* @throws \InvalidArgumentException
*
* @return bool
*/
public function set(string $name, string $value)
{
if ('' === $name) {
throw new InvalidArgumentException('Expected name to be a non-empty string.');
}
return $this->writer->write($name, $value);
}
/**
* Clear an environment variable.
*
* @param string $name
*
* @throws \InvalidArgumentException
*
* @return bool
*/
public function clear(string $name)
{
if ('' === $name) {
throw new InvalidArgumentException('Expected name to be a non-empty string.');
}
return $this->writer->delete($name);
}
}
================================================
FILE: src/Repository/RepositoryBuilder.php
================================================
readers = $readers;
$this->writers = $writers;
$this->immutable = $immutable;
$this->allowList = $allowList;
}
/**
* Create a new repository builder instance with no adapters added.
*
* @return \Dotenv\Repository\RepositoryBuilder
*/
public static function createWithNoAdapters()
{
return new self();
}
/**
* Create a new repository builder instance with the default adapters added.
*
* @return \Dotenv\Repository\RepositoryBuilder
*/
public static function createWithDefaultAdapters()
{
$adapters = \iterator_to_array(self::defaultAdapters());
return new self($adapters, $adapters);
}
/**
* Return the array of default adapters.
*
* @return \Generator<\Dotenv\Repository\Adapter\AdapterInterface>
*/
private static function defaultAdapters()
{
foreach (self::DEFAULT_ADAPTERS as $adapter) {
$instance = $adapter::create();
if ($instance->isDefined()) {
yield $instance->get();
}
}
}
/**
* Determine if the given name if of an adapterclass.
*
* @param string $name
*
* @return bool
*/
private static function isAnAdapterClass(string $name)
{
if (!\class_exists($name)) {
return false;
}
return (new ReflectionClass($name))->implementsInterface(AdapterInterface::class);
}
/**
* Creates a repository builder with the given reader added.
*
* Accepts either a reader instance, or a class-string for an adapter. If
* the adapter is not supported, then we silently skip adding it.
*
* @param \Dotenv\Repository\Adapter\ReaderInterface|string $reader
*
* @throws \InvalidArgumentException
*
* @return \Dotenv\Repository\RepositoryBuilder
*/
public function addReader($reader)
{
if (!(\is_string($reader) && self::isAnAdapterClass($reader)) && !($reader instanceof ReaderInterface)) {
throw new InvalidArgumentException(
\sprintf(
'Expected either an instance of %s or a class-string implementing %s',
ReaderInterface::class,
AdapterInterface::class
)
);
}
$optional = Some::create($reader)->flatMap(static function ($reader) {
return \is_string($reader) ? $reader::create() : Some::create($reader);
});
$readers = \array_merge($this->readers, \iterator_to_array($optional));
return new self($readers, $this->writers, $this->immutable, $this->allowList);
}
/**
* Creates a repository builder with the given writer added.
*
* Accepts either a writer instance, or a class-string for an adapter. If
* the adapter is not supported, then we silently skip adding it.
*
* @param \Dotenv\Repository\Adapter\WriterInterface|string $writer
*
* @throws \InvalidArgumentException
*
* @return \Dotenv\Repository\RepositoryBuilder
*/
public function addWriter($writer)
{
if (!(\is_string($writer) && self::isAnAdapterClass($writer)) && !($writer instanceof WriterInterface)) {
throw new InvalidArgumentException(
\sprintf(
'Expected either an instance of %s or a class-string implementing %s',
WriterInterface::class,
AdapterInterface::class
)
);
}
$optional = Some::create($writer)->flatMap(static function ($writer) {
return \is_string($writer) ? $writer::create() : Some::create($writer);
});
$writers = \array_merge($this->writers, \iterator_to_array($optional));
return new self($this->readers, $writers, $this->immutable, $this->allowList);
}
/**
* Creates a repository builder with the given adapter added.
*
* Accepts either an adapter instance, or a class-string for an adapter. If
* the adapter is not supported, then we silently skip adding it. We will
* add the adapter as both a reader and a writer.
*
* @param \Dotenv\Repository\Adapter\WriterInterface|string $adapter
*
* @throws \InvalidArgumentException
*
* @return \Dotenv\Repository\RepositoryBuilder
*/
public function addAdapter($adapter)
{
if (!(\is_string($adapter) && self::isAnAdapterClass($adapter)) && !($adapter instanceof AdapterInterface)) {
throw new InvalidArgumentException(
\sprintf(
'Expected either an instance of %s or a class-string implementing %s',
WriterInterface::class,
AdapterInterface::class
)
);
}
$optional = Some::create($adapter)->flatMap(static function ($adapter) {
return \is_string($adapter) ? $adapter::create() : Some::create($adapter);
});
$readers = \array_merge($this->readers, \iterator_to_array($optional));
$writers = \array_merge($this->writers, \iterator_to_array($optional));
return new self($readers, $writers, $this->immutable, $this->allowList);
}
/**
* Creates a repository builder with mutability enabled.
*
* @return \Dotenv\Repository\RepositoryBuilder
*/
public function immutable()
{
return new self($this->readers, $this->writers, true, $this->allowList);
}
/**
* Creates a repository builder with the given allow list.
*
* @param string[]|null $allowList
*
* @return \Dotenv\Repository\RepositoryBuilder
*/
public function allowList(?array $allowList = null)
{
return new self($this->readers, $this->writers, $this->immutable, $allowList);
}
/**
* Creates a new repository instance.
*
* @return \Dotenv\Repository\RepositoryInterface
*/
public function make()
{
$reader = new MultiReader($this->readers);
$writer = new MultiWriter($this->writers);
if ($this->immutable) {
$writer = new ImmutableWriter($writer, $reader);
}
if ($this->allowList !== null) {
$writer = new GuardedWriter($writer, $this->allowList);
}
return new AdapterRepository($reader, $writer);
}
}
================================================
FILE: src/Repository/RepositoryInterface.php
================================================
*/
public static function read(array $filePaths, bool $shortCircuit = true, ?string $fileEncoding = null)
{
$output = [];
foreach ($filePaths as $filePath) {
$content = self::readFromFile($filePath, $fileEncoding);
if ($content->isDefined()) {
$output[$filePath] = $content->get();
if ($shortCircuit) {
break;
}
}
}
return $output;
}
/**
* Read the given file.
*
* @param string $path
* @param string|null $encoding
*
* @throws \Dotenv\Exception\InvalidEncodingException
*
* @return \PhpOption\Option
*/
private static function readFromFile(string $path, ?string $encoding = null)
{
/** @var Option */
$content = Option::fromValue(@\file_get_contents($path), false);
return $content->flatMap(static function (string $content) use ($encoding) {
return Str::utf8($content, $encoding)->mapError(static function (string $error) {
throw new InvalidEncodingException($error);
})->success();
});
}
}
================================================
FILE: src/Store/FileStore.php
================================================
filePaths = $filePaths;
$this->shortCircuit = $shortCircuit;
$this->fileEncoding = $fileEncoding;
}
/**
* Read the content of the environment file(s).
*
* @throws \Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidPathException
*
* @return string
*/
public function read()
{
if ($this->filePaths === []) {
throw new InvalidPathException('At least one environment file path must be provided.');
}
$contents = Reader::read($this->filePaths, $this->shortCircuit, $this->fileEncoding);
if (\count($contents) > 0) {
return \implode("\n", $contents);
}
throw new InvalidPathException(
\sprintf('Unable to read any of the environment file(s) at [%s].', \implode(', ', $this->filePaths))
);
}
}
================================================
FILE: src/Store/StoreBuilder.php
================================================
paths = $paths;
$this->names = $names;
$this->shortCircuit = $shortCircuit;
$this->fileEncoding = $fileEncoding;
}
/**
* Create a new store builder instance with no names.
*
* @return \Dotenv\Store\StoreBuilder
*/
public static function createWithNoNames()
{
return new self();
}
/**
* Create a new store builder instance with the default name.
*
* @return \Dotenv\Store\StoreBuilder
*/
public static function createWithDefaultName()
{
return new self([], [self::DEFAULT_NAME]);
}
/**
* Creates a store builder with the given path added.
*
* @param string $path
*
* @return \Dotenv\Store\StoreBuilder
*/
public function addPath(string $path)
{
return new self(\array_merge($this->paths, [$path]), $this->names, $this->shortCircuit, $this->fileEncoding);
}
/**
* Creates a store builder with the given name added.
*
* @param string $name
*
* @return \Dotenv\Store\StoreBuilder
*/
public function addName(string $name)
{
return new self($this->paths, \array_merge($this->names, [$name]), $this->shortCircuit, $this->fileEncoding);
}
/**
* Creates a store builder with short circuit mode enabled.
*
* @return \Dotenv\Store\StoreBuilder
*/
public function shortCircuit()
{
return new self($this->paths, $this->names, true, $this->fileEncoding);
}
/**
* Creates a store builder with the specified file encoding.
*
* @param string|null $fileEncoding
*
* @return \Dotenv\Store\StoreBuilder
*/
public function fileEncoding(?string $fileEncoding = null)
{
return new self($this->paths, $this->names, $this->shortCircuit, $fileEncoding);
}
/**
* Creates a new store instance.
*
* @return \Dotenv\Store\StoreInterface
*/
public function make()
{
return new FileStore(
Paths::filePaths($this->paths, $this->names),
$this->shortCircuit,
$this->fileEncoding
);
}
}
================================================
FILE: src/Store/StoreInterface.php
================================================
content = $content;
}
/**
* Read the content of the environment file(s).
*
* @return string
*/
public function read()
{
return $this->content;
}
}
================================================
FILE: src/Util/Regex.php
================================================
*/
public static function matches(string $pattern, string $subject)
{
return self::pregAndWrap(static function (string $subject) use ($pattern) {
return @\preg_match($pattern, $subject) === 1;
}, $subject);
}
/**
* Perform a preg match all, wrapping up the result.
*
* @param string $pattern
* @param string $subject
*
* @return \GrahamCampbell\ResultType\Result
*/
public static function occurrences(string $pattern, string $subject)
{
return self::pregAndWrap(static function (string $subject) use ($pattern) {
return (int) @\preg_match_all($pattern, $subject);
}, $subject);
}
/**
* Perform a preg replace callback, wrapping up the result.
*
* @param string $pattern
* @param callable(string[]): string $callback
* @param string $subject
* @param int|null $limit
*
* @return \GrahamCampbell\ResultType\Result
*/
public static function replaceCallback(string $pattern, callable $callback, string $subject, ?int $limit = null)
{
return self::pregAndWrap(static function (string $subject) use ($pattern, $callback, $limit) {
return (string) @\preg_replace_callback($pattern, $callback, $subject, $limit ?? -1);
}, $subject);
}
/**
* Perform a preg split, wrapping up the result.
*
* @param string $pattern
* @param string $subject
*
* @return \GrahamCampbell\ResultType\Result
*/
public static function split(string $pattern, string $subject)
{
return self::pregAndWrap(static function (string $subject) use ($pattern) {
/** @var string[] */
return (array) @\preg_split($pattern, $subject);
}, $subject);
}
/**
* Perform a preg operation, wrapping up the result.
*
* @template V
*
* @param callable(string): V $operation
* @param string $subject
*
* @return \GrahamCampbell\ResultType\Result
*/
private static function pregAndWrap(callable $operation, string $subject)
{
$result = $operation($subject);
if (\preg_last_error() !== \PREG_NO_ERROR) {
/** @var \GrahamCampbell\ResultType\Result */
return Error::create(\preg_last_error_msg());
}
/** @var \GrahamCampbell\ResultType\Result */
return Success::create($result);
}
}
================================================
FILE: src/Util/Str.php
================================================
*/
public static function utf8(string $input, ?string $encoding = null)
{
if ($encoding !== null && !\in_array($encoding, \mb_list_encodings(), true)) {
/** @var \GrahamCampbell\ResultType\Result */
return Error::create(
\sprintf('Illegal character encoding [%s] specified.', $encoding)
);
}
$converted = $encoding === null ?
@\mb_convert_encoding($input, 'UTF-8') :
@\mb_convert_encoding($input, 'UTF-8', $encoding);
if (!is_string($converted)) {
/** @var \GrahamCampbell\ResultType\Result */
return Error::create(
\sprintf('Conversion from encoding [%s] failed.', $encoding ?? 'NULL')
);
}
/**
* this is for support UTF-8 with BOM encoding
* @see https://en.wikipedia.org/wiki/Byte_order_mark
* @see https://github.com/vlucas/phpdotenv/issues/500
*/
if (\substr($converted, 0, 3) == "\xEF\xBB\xBF") {
$converted = \substr($converted, 3);
}
/** @var \GrahamCampbell\ResultType\Result */
return Success::create($converted);
}
/**
* Search for a given substring of the input.
*
* @param string $haystack
* @param string $needle
*
* @return \PhpOption\Option
*/
public static function pos(string $haystack, string $needle)
{
/** @var \PhpOption\Option */
return Option::fromValue(\mb_strpos($haystack, $needle, 0, 'UTF-8'), false);
}
/**
* Grab the specified substring of the input.
*
* @param string $input
* @param int $start
* @param int|null $length
*
* @return string
*/
public static function substr(string $input, int $start, ?int $length = null)
{
return \mb_substr($input, $start, $length, 'UTF-8');
}
/**
* Compute the length of the given string.
*
* @param string $input
*
* @return int
*/
public static function len(string $input)
{
return \mb_strlen($input, 'UTF-8');
}
}
================================================
FILE: src/Validator.php
================================================
repository = $repository;
$this->variables = $variables;
}
/**
* Assert that each variable is present.
*
* @throws \Dotenv\Exception\ValidationException
*
* @return \Dotenv\Validator
*/
public function required()
{
return $this->assert(
static function (?string $value) {
return $value !== null;
},
'is missing'
);
}
/**
* Assert that each variable is not empty.
*
* @throws \Dotenv\Exception\ValidationException
*
* @return \Dotenv\Validator
*/
public function notEmpty()
{
return $this->assertNullable(
static function (string $value) {
return Str::len(\trim($value)) > 0;
},
'is empty'
);
}
/**
* Assert that each specified variable is an integer.
*
* @throws \Dotenv\Exception\ValidationException
*
* @return \Dotenv\Validator
*/
public function isInteger()
{
return $this->assertNullable(
static function (string $value) {
return \ctype_digit($value);
},
'is not an integer'
);
}
/**
* Assert that each specified variable is a boolean.
*
* @throws \Dotenv\Exception\ValidationException
*
* @return \Dotenv\Validator
*/
public function isBoolean()
{
return $this->assertNullable(
static function (string $value) {
if ($value === '') {
return false;
}
return \filter_var($value, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE) !== null;
},
'is not a boolean'
);
}
/**
* Assert that each variable is amongst the given choices.
*
* @param string[] $choices
*
* @throws \Dotenv\Exception\ValidationException
*
* @return \Dotenv\Validator
*/
public function allowedValues(array $choices)
{
return $this->assertNullable(
static function (string $value) use ($choices) {
return \in_array($value, $choices, true);
},
\sprintf('is not one of [%s]', \implode(', ', $choices))
);
}
/**
* Assert that each variable matches the given regular expression.
*
* @param string $regex
*
* @throws \Dotenv\Exception\ValidationException
*
* @return \Dotenv\Validator
*/
public function allowedRegexValues(string $regex)
{
return $this->assertNullable(
static function (string $value) use ($regex) {
return Regex::matches($regex, $value)->success()->getOrElse(false);
},
\sprintf('does not match "%s"', $regex)
);
}
/**
* Assert that the callback returns true for each variable.
*
* @param callable(?string):bool $callback
* @param string $message
*
* @throws \Dotenv\Exception\ValidationException
*
* @return \Dotenv\Validator
*/
public function assert(callable $callback, string $message)
{
$failing = [];
foreach ($this->variables as $variable) {
if ($callback($this->repository->get($variable)) === false) {
$failing[] = \sprintf('%s %s', $variable, $message);
}
}
if (\count($failing) > 0) {
throw new ValidationException(\sprintf(
'One or more environment variables failed assertions: %s.',
\implode(', ', $failing)
));
}
return $this;
}
/**
* Assert that the callback returns true for each variable.
*
* Skip checking null variable values.
*
* @param callable(string):bool $callback
* @param string $message
*
* @throws \Dotenv\Exception\ValidationException
*
* @return \Dotenv\Validator
*/
public function assertNullable(callable $callback, string $message)
{
return $this->assert(
static function (?string $value) use ($callback) {
if ($value === null) {
return true;
}
return $callback($value);
},
$message
);
}
}
================================================
FILE: tests/Dotenv/DotenvTest.php
================================================
expectException(InvalidPathException::class);
$this->expectExceptionMessage('Unable to read any of the environment file(s) at');
$dotenv->load();
}
public function testDotenvThrowsExceptionIfUnableToLoadFiles()
{
$dotenv = Dotenv::createMutable([__DIR__, __DIR__.'/foo/bar']);
$this->expectException(InvalidPathException::class);
$this->expectExceptionMessage('Unable to read any of the environment file(s) at');
$dotenv->load();
}
public function testDotenvThrowsExceptionWhenNoFiles()
{
$dotenv = Dotenv::createMutable([]);
$this->expectException(InvalidPathException::class);
$this->expectExceptionMessage('At least one environment file path must be provided.');
$dotenv->load();
}
public function testDotenvTriesPathsToLoad()
{
$dotenv = Dotenv::createMutable([__DIR__, self::$folder]);
self::assertCount(4, $dotenv->load());
}
public function testDotenvTriesPathsToLoadTwice()
{
$dotenv = Dotenv::createMutable([__DIR__, self::$folder]);
self::assertCount(4, $dotenv->load());
$dotenv = Dotenv::createImmutable([__DIR__, self::$folder]);
self::assertCount(0, $dotenv->load());
}
public function testDotenvTriesPathsToSafeLoad()
{
$dotenv = Dotenv::createMutable([__DIR__, self::$folder]);
self::assertCount(4, $dotenv->safeLoad());
}
public function testDotenvSkipsLoadingIfFileIsMissing()
{
$dotenv = Dotenv::createMutable(__DIR__);
self::assertSame([], $dotenv->safeLoad());
}
public function testDotenvLoadsEnvironmentVars()
{
$dotenv = Dotenv::createMutable(self::$folder);
self::assertSame(
['FOO' => 'bar', 'BAR' => 'baz', 'SPACED' => 'with spaces', 'NULL' => ''],
$dotenv->load()
);
self::assertSame('bar', $_SERVER['FOO']);
self::assertSame('baz', $_SERVER['BAR']);
self::assertSame('with spaces', $_SERVER['SPACED']);
self::assertEmpty($_SERVER['NULL']);
}
public function testDotenvLoadsEnvironmentVarsMultipleWithShortCircuitMode()
{
$dotenv = Dotenv::createMutable(self::$folder, ['.env', 'example.env']);
self::assertSame(
['FOO' => 'bar', 'BAR' => 'baz', 'SPACED' => 'with spaces', 'NULL' => ''],
$dotenv->load()
);
}
public function testDotenvLoadsEnvironmentVarsMultipleWithoutShortCircuitMode()
{
$dotenv = Dotenv::createMutable(self::$folder, ['.env', 'example.env'], false);
self::assertSame(
['FOO' => 'bar', 'BAR' => 'baz', 'SPACED' => 'with spaces', 'NULL' => '', 'EG' => 'example'],
$dotenv->load()
);
}
public function testCommentedDotenvLoadsEnvironmentVars()
{
$dotenv = Dotenv::createMutable(self::$folder, 'commented.env');
$dotenv->load();
self::assertSame('bar', $_SERVER['CFOO']);
self::assertFalse(isset($_SERVER['CBAR']));
self::assertFalse(isset($_SERVER['CZOO']));
self::assertSame('with spaces', $_SERVER['CSPACED']);
self::assertSame('a value with a # character', $_SERVER['CQUOTES']);
self::assertSame('a value with a # character & a quote " character inside quotes', $_SERVER['CQUOTESWITHQUOTE']);
self::assertEmpty($_SERVER['CNULL']);
self::assertEmpty($_SERVER['EMPTY']);
self::assertEmpty($_SERVER['EMPTY2']);
self::assertSame('foo', $_SERVER['FOOO']);
}
public function testQuotedDotenvLoadsEnvironmentVars()
{
$dotenv = Dotenv::createMutable(self::$folder, 'quoted.env');
$dotenv->load();
self::assertSame('bar', $_SERVER['QFOO']);
self::assertSame('baz', $_SERVER['QBAR']);
self::assertSame('with spaces', $_SERVER['QSPACED']);
self::assertEmpty(\getenv('QNULL'));
self::assertSame('pgsql:host=localhost;dbname=test', $_SERVER['QEQUALS']);
self::assertSame('test some escaped characters like a quote (") or maybe a backslash (\\)', $_SERVER['QESCAPED']);
self::assertSame('iiiiviiiixiiiiviiii\\n', $_SERVER['QSLASH']);
self::assertSame('iiiiviiiixiiiiviiii\\\\n', $_SERVER['SQSLASH']);
}
public function testLargeDotenvLoadsEnvironmentVars()
{
$dotenv = Dotenv::createMutable(self::$folder, 'large.env');
$dotenv->load();
self::assertSame(2730, \strlen($_SERVER['LARGE']));
self::assertSame(8192, \strlen($_SERVER['HUGE']));
}
public function testDotenvLoadsMultibyteVars()
{
$dotenv = Dotenv::createMutable(self::$folder, 'multibyte.env');
$dotenv->load();
self::assertSame('Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě', $_SERVER['MB1']);
self::assertSame('行内支付', $_SERVER['MB2']);
self::assertSame('🚀', $_SERVER['APP_ENV']);
}
public function testDotenvLoadsMultibyteUTF8Vars()
{
$dotenv = Dotenv::createMutable(self::$folder, 'multibyte.env', false, 'UTF-8');
$dotenv->load();
self::assertSame('Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě', $_SERVER['MB1']);
self::assertSame('行内支付', $_SERVER['MB2']);
self::assertSame('🚀', $_SERVER['APP_ENV']);
}
public function testDotenvLoadWithInvalidEncoding()
{
$dotenv = Dotenv::createMutable(self::$folder, 'multibyte.env', false, 'UTF-88');
$this->expectException(InvalidEncodingException::class);
$this->expectExceptionMessage('Illegal character encoding [UTF-88] specified.');
$dotenv->load();
}
public function testDotenvLoadsMultibyteWindowsVars()
{
$dotenv = Dotenv::createMutable(self::$folder, 'windows.env', false, 'Windows-1252');
$dotenv->load();
self::assertSame('ñá', $_SERVER['MBW']);
}
public function testMultipleDotenvLoadsEnvironmentVars()
{
$dotenv = Dotenv::createMutable(self::$folder, 'multiple.env');
$dotenv->load();
self::assertSame('bar', $_SERVER['MULTI1']);
self::assertSame('foo', $_SERVER['MULTI2']);
}
public function testExportedDotenvLoadsEnvironmentVars()
{
$dotenv = Dotenv::createMutable(self::$folder, 'exported.env');
$dotenv->load();
self::assertSame('bar', $_SERVER['EFOO']);
self::assertSame('baz', $_SERVER['EBAR']);
self::assertSame('with spaces', $_SERVER['ESPACED']);
self::assertSame('123', $_SERVER['EDQUOTED']);
self::assertSame('456', $_SERVER['ESQUOTED']);
self::assertEmpty($_SERVER['ENULL']);
}
public function testDotenvLoadsEnvGlobals()
{
$dotenv = Dotenv::createMutable(self::$folder);
$dotenv->load();
self::assertSame('bar', $_SERVER['FOO']);
self::assertSame('baz', $_SERVER['BAR']);
self::assertSame('with spaces', $_SERVER['SPACED']);
self::assertEmpty($_SERVER['NULL']);
}
public function testDotenvLoadsServerGlobals()
{
$dotenv = Dotenv::createMutable(self::$folder);
$dotenv->load();
self::assertSame('bar', $_ENV['FOO']);
self::assertSame('baz', $_ENV['BAR']);
self::assertSame('with spaces', $_ENV['SPACED']);
self::assertEmpty($_ENV['NULL']);
}
public function testDotenvNestedEnvironmentVars()
{
$dotenv = Dotenv::createMutable(self::$folder, 'nested.env');
$dotenv->load();
self::assertSame('{$NVAR1} {$NVAR2}', $_ENV['NVAR3']); // not resolved
self::assertSame('Hellō World!', $_ENV['NVAR4']);
self::assertSame('$NVAR1 {NVAR2}', $_ENV['NVAR5']); // not resolved
self::assertSame('Special Value', $_ENV['N.VAR6']); // new '.' (dot) in var name
self::assertSame('Special Value', $_ENV['NVAR7']); // nested '.' (dot) variable
self::assertSame('', $_ENV['NVAR8']); // nested variable is empty string
self::assertSame('', $_ENV['NVAR9']); // nested variable is empty string
self::assertSame('${NVAR888}', $_ENV['NVAR10']); // nested variable is not set
self::assertSame('NVAR1', $_ENV['NVAR11']);
self::assertSame('Hellō', $_ENV['NVAR12']);
self::assertSame('${${NVAR11}}', $_ENV['NVAR13']); // single quotes
self::assertSame('${NVAR1} ${NVAR2}', $_ENV['NVAR14']); // single quotes
self::assertSame('${NVAR1} ${NVAR2}', $_ENV['NVAR15']); // escaped
}
public function testDotenvNullFileArgumentUsesDefault()
{
$dotenv = Dotenv::createMutable(self::$folder, null);
$dotenv->load();
self::assertSame('bar', $_SERVER['FOO']);
}
/**
* The fixture data has whitespace between the key and in the value string.
*
* Test that these keys are trimmed down.
*/
public function testDotenvTrimmedKeys()
{
$dotenv = Dotenv::createMutable(self::$folder, 'quoted.env');
$dotenv->load();
self::assertSame('no space', $_SERVER['QWHITESPACE']);
}
public function testDotenvLoadDoesNotOverwriteEnv()
{
\putenv('IMMUTABLE=true');
$dotenv = Dotenv::createImmutable(self::$folder, 'immutable.env');
$dotenv->load();
self::assertSame('true', \getenv('IMMUTABLE'));
}
public function testDotenvLoadAfterOverload()
{
\putenv('IMMUTABLE=true');
$dotenv = Dotenv::createUnsafeMutable(self::$folder, 'immutable.env');
$dotenv->load();
self::assertSame('false', \getenv('IMMUTABLE'));
}
public function testDotenvOverloadAfterLoad()
{
\putenv('IMMUTABLE=true');
$dotenv = Dotenv::createUnsafeImmutable(self::$folder, 'immutable.env');
$dotenv->load();
self::assertSame('true', \getenv('IMMUTABLE'));
}
public function testDotenvOverloadDoesOverwriteEnv()
{
$dotenv = Dotenv::createUnsafeMutable(self::$folder, 'mutable.env');
$dotenv->load();
self::assertSame('true', \getenv('MUTABLE'));
}
public function testDotenvAllowsSpecialCharacters()
{
$dotenv = Dotenv::createUnsafeMutable(self::$folder, 'specialchars.env');
$dotenv->load();
self::assertSame('$a6^C7k%zs+e^.jvjXk', \getenv('SPVAR1'));
self::assertSame('?BUty3koaV3%GA*hMAwH}B', \getenv('SPVAR2'));
self::assertSame('jdgEB4{QgEC]HL))&GcXxokB+wqoN+j>xkV7K?m$r', \getenv('SPVAR3'));
self::assertSame('22222:22#2^{', \getenv('SPVAR4'));
self::assertSame('test some escaped characters like a quote " or maybe a backslash \\', \getenv('SPVAR5'));
self::assertSame('secret!@', \getenv('SPVAR6'));
self::assertSame('secret!@#', \getenv('SPVAR7'));
self::assertSame('secret!@#', \getenv('SPVAR8'));
}
public function testMultilineLoading()
{
$dotenv = Dotenv::createUnsafeMutable(self::$folder, 'multiline.env');
$dotenv->load();
self::assertSame("test\n test\"test\"\n test", \getenv('TEST'));
self::assertSame("test\ntest", \getenv('TEST_ND'));
self::assertSame('test\\ntest', \getenv('TEST_NS'));
self::assertSame('https://vision.googleapis.com/v1/images:annotate?key=', \getenv('TEST_EQD'));
self::assertSame('https://vision.googleapis.com/v1/images:annotate?key=', \getenv('TEST_EQS'));
}
public function testEmptyLoading()
{
$dotenv = Dotenv::createImmutable(self::$folder, 'empty.env');
self::assertSame(['EMPTY_VAR' => null], $dotenv->load());
}
public function testUnicodeVarNames()
{
$dotenv = Dotenv::createImmutable(self::$folder, 'unicodevarnames.env');
$dotenv->load();
self::assertSame('Skybert', $_SERVER['AlbertÅberg']);
self::assertSame('2022-04-01T00:00', $_SERVER['ДатаЗакрытияРасчетногоПериода']);
}
public function testDirectConstructor()
{
$repository = RepositoryBuilder::createWithDefaultAdapters()->make();
$store = StoreBuilder::createWithDefaultName()->addPath(self::$folder)->make();
$dotenv = new Dotenv($store, new Parser(), new Loader(), $repository);
self::assertSame([
'FOO' => 'bar',
'BAR' => 'baz',
'SPACED' => 'with spaces',
'NULL' => '',
], $dotenv->load());
}
public function testDotenvParseExample1()
{
$output = Dotenv::parse(
"BASE_DIR=\"/var/webroot/project-root\"\nCACHE_DIR=\"\${BASE_DIR}/cache\"\nTMP_DIR=\"\${BASE_DIR}/tmp\"\n"
);
self::assertSame($output, [
'BASE_DIR' => '/var/webroot/project-root',
'CACHE_DIR' => '/var/webroot/project-root/cache',
'TMP_DIR' => '/var/webroot/project-root/tmp',
]);
}
public function testDotenvParseExample2()
{
$output = Dotenv::parse("FOO=Bar\nBAZ=\"Hello \${FOO}\"");
self::assertSame($output, ['FOO' => 'Bar', 'BAZ' => 'Hello Bar']);
}
public function testDotenvParseEmptyCase()
{
$output = Dotenv::parse('');
self::assertSame($output, []);
}
}
================================================
FILE: tests/Dotenv/Loader/LoaderTest.php
================================================
addWriter(ArrayAdapter::class)->make();
$loader = new Loader();
$content = "NVAR1=\"Hello\"\nNVAR2=\"World!\"\nNVAR3=\"{\$NVAR1} {\$NVAR2}\"\nNVAR4=\"\${NVAR1} \${NVAR2}\"";
$expected = ['NVAR1' => 'Hello', 'NVAR2' => 'World!', 'NVAR3' => '{$NVAR1} {$NVAR2}', 'NVAR4' => '${NVAR1} ${NVAR2}'];
self::assertSame($expected, $loader->load($repository, (new Parser())->parse($content)));
}
public function testLoaderWithAllowList()
{
$adapter = ArrayAdapter::create()->get();
$repository = RepositoryBuilder::createWithNoAdapters()->addReader($adapter)->addWriter($adapter)->allowList(['FOO'])->make();
$loader = new Loader();
self::assertSame(['FOO' => 'Hello'], $loader->load($repository, (new Parser())->parse("FOO=\"Hello\"\nBAR=\"World!\"\n")));
self::assertTrue($adapter->read('FOO')->isDefined());
self::assertSame('Hello', $adapter->read('FOO')->get());
self::assertFalse($adapter->read('BAR')->isDefined());
}
public function testLoaderWithGarbage()
{
$adapter = ArrayAdapter::create()->get();
$repository = RepositoryBuilder::createWithNoAdapters()->make();
$loader = new Loader();
$this->expectException(InvalidFileException::class);
$this->expectExceptionMessage('Failed to parse dotenv file. Encountered unexpected whitespace at ["""].');
$loader->load($repository, (new Parser())->parse('FOO="""'));
}
/**
* @return array[]
*/
public static function providesAdapters()
{
return [
[ArrayAdapter::create()->get()],
[EnvConstAdapter::class],
[ServerConstAdapter::class],
];
}
/**
* @dataProvider providesAdapters
*
* @param \Dotenv\Repository\Adapter\AdapterInterface|string $adapter
*/
public function testLoaderWithSpecificAdapter($adapter)
{
$repository = RepositoryBuilder::createWithNoAdapters()->addReader($adapter)->addWriter($adapter)->make();
$loader = new Loader();
$content = "NVAR1=\"Hello\"\nNVAR2=\"World!\"\nNVAR3=\"{\$NVAR1} {\$NVAR2}\"\nNVAR4=\"\${NVAR1} \${NVAR2}\"";
$expected = ['NVAR1' => 'Hello', 'NVAR2' => 'World!', 'NVAR3' => '{$NVAR1} {$NVAR2}', 'NVAR4' => 'Hello World!'];
self::assertSame($expected, $loader->load($repository, (new Parser())->parse($content)));
}
}
================================================
FILE: tests/Dotenv/Parser/EntryParserTest.php
================================================
checkPositiveResult($result, 'FOO', 'BAR');
}
public function testNullParse()
{
$result = EntryParser::parse('FOO');
$this->checkEmptyResult($result, 'FOO');
}
public function testUnicodeNameParse()
{
$result = EntryParser::parse('FOOƱ=BAZ');
$this->checkPositiveResult($result, 'FOOƱ', 'BAZ');
}
public function testQuotesParse()
{
$result = EntryParser::parse("FOO=\"BAR \n\"");
$this->checkPositiveResult($result, 'FOO', "BAR \n");
}
public function testNewlineParse()
{
$result = EntryParser::parse('FOO="\n"');
$this->checkPositiveResult($result, 'FOO', "\n");
}
public function testTabParseDouble()
{
$result = EntryParser::parse('FOO="\t"');
$this->checkPositiveResult($result, 'FOO', "\t");
}
public function testTabParseSingle()
{
$result = EntryParser::parse('FOO=\'\t\'');
$this->checkPositiveResult($result, 'FOO', '\t');
}
public function testNonEscapeParse1()
{
$result = EntryParser::parse('FOO=\n\v');
$this->checkPositiveResult($result, 'FOO', '\n\v');
}
public function testNonEscapeParse2()
{
$result = EntryParser::parse('FOO=\q');
$this->checkPositiveResult($result, 'FOO', '\q');
}
public function testBadEscapeParse()
{
$result = EntryParser::parse('FOO="\q"');
$this->checkErrorResult($result, 'Encountered an unexpected escape sequence at ["\q"].');
}
public function testInlineVariable()
{
$result = EntryParser::parse('FOO=$BAR');
$this->checkPositiveResult($result, 'FOO', '$BAR', [0]);
}
public function testInlineVariableOffset()
{
$result = EntryParser::parse('FOO=AAA$BAR');
$this->checkPositiveResult($result, 'FOO', 'AAA$BAR', [3]);
}
public function testInlineVariables()
{
$result = EntryParser::parse('FOO="TEST $BAR $$BAZ"');
$this->checkPositiveResult($result, 'FOO', 'TEST $BAR $$BAZ', [11, 10, 5]);
}
public function testNonInlineVariable()
{
$result = EntryParser::parse('FOO=\'TEST $BAR $$BAZ\'');
$this->checkPositiveResult($result, 'FOO', 'TEST $BAR $$BAZ');
self::assertTrue($result->success()->isDefined());
}
public function testWhitespaceParse()
{
$result = EntryParser::parse("FOO=\"\n\"");
$this->checkPositiveResult($result, 'FOO', "\n");
}
public function testExportParse()
{
$result = EntryParser::parse('export FOO="bar baz"');
$this->checkPositiveResult($result, 'FOO', 'bar baz');
}
public function testExportParseTab()
{
$result = EntryParser::parse("export\t\"FOO\"='bar baz'");
$this->checkPositiveResult($result, 'FOO', 'bar baz');
}
public function testExportParseFail()
{
$result = EntryParser::parse('export "FOO="bar baz"');
$this->checkErrorResult($result, 'Encountered an invalid name at ["FOO].');
}
public function testClosingSlashParse()
{
$result = EntryParser::parse('SPVAR5="test some escaped characters like a quote \\" or maybe a backslash \\\\" # not escaped');
$this->checkPositiveResult($result, 'SPVAR5', 'test some escaped characters like a quote " or maybe a backslash \\');
}
public function testParseInvalidSpaces()
{
$result = EntryParser::parse('FOO=bar baz');
$this->checkErrorResult($result, 'Encountered unexpected whitespace at [bar baz].');
}
public function testParseStrayEquals()
{
$result = EntryParser::parse('=');
$this->checkErrorResult($result, 'Encountered an unexpected equals at [=].');
}
public function testParseInvalidName()
{
$result = EntryParser::parse('FOO_ASD!=BAZ');
$this->checkErrorResult($result, 'Encountered an invalid name at [FOO_ASD!].');
}
public function testParserEscapingDouble()
{
$result = EntryParser::parse('FOO_BAD="iiiiviiiixiiiiviiii\\a"');
$this->checkErrorResult($result, 'Encountered an unexpected escape sequence at ["iiiiviiiixiiiiviiii\a"].');
}
public function testParserEscapingSingle()
{
$result = EntryParser::parse('FOO_BAD=\'iiiiviiiixiiiiviiii\\a\'');
$this->checkPositiveResult($result, 'FOO_BAD', 'iiiiviiiixiiiiviiii\\a');
}
public function testParserMissingClosingSingleQuote()
{
$result = EntryParser::parse('TEST=\'erert');
$this->checkErrorResult($result, 'Encountered a missing closing quote at [\'erert].');
}
public function testParserMissingClosingDoubleQuote()
{
$result = EntryParser::parse('TEST="erert');
$this->checkErrorResult($result, 'Encountered a missing closing quote at ["erert].');
}
public function testParserMissingClosingQuotes()
{
$result = EntryParser::parse("TEST=\"erert\nTEST='erert\n");
$this->checkErrorResult($result, 'Encountered a missing closing quote at ["erert].');
}
public function testParserClosingQuoteWithEscape()
{
$result = EntryParser::parse('TEST="\\');
$this->checkErrorResult($result, 'Encountered a missing closing quote at ["\\].');
}
/**
* @param \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry,string> $result
* @param string $name
* @param string $chars
* @param int[] $vars
*
* @return void
*/
private function checkPositiveResult(Result $result, string $name, string $chars, array $vars = [])
{
self::assertTrue($result->success()->isDefined());
$entry = $result->success()->get();
self::assertInstanceOf(Entry::class, $entry);
self::assertSame($name, $entry->getName());
self::assertTrue($entry->getValue()->isDefined());
$value = $entry->getValue()->get();
self::assertInstanceOf(Value::class, $value);
self::assertSame($chars, $value->getChars());
self::assertSame($vars, $value->getVars());
}
/**
* @param \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry,string> $result
* @param string $name
*
* @return void
*/
private function checkEmptyResult(Result $result, string $name)
{
self::assertTrue($result->success()->isDefined());
$entry = $result->success()->get();
self::assertInstanceOf(Entry::class, $entry);
self::assertSame('FOO', $entry->getName());
self::assertFalse($entry->getValue()->isDefined());
}
/**
* @param \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry,string> $result
* @param string $error
*
* @return void
*/
private function checkErrorResult(Result $result, string $error)
{
self::assertTrue($result->error()->isDefined());
self::assertSame($error, $result->error()->get());
}
}
================================================
FILE: tests/Dotenv/Parser/LexerTest.php
================================================
success()->isDefined());
$expected = [
'ASSERTVAR1=val1',
'ASSERTVAR2=""',
'ASSERTVAR3="val3 "',
'ASSERTVAR4="0" # empty looking value',
'ASSERTVAR5="#foo"',
"ASSERTVAR6=\"val1\nval2\"",
"ASSERTVAR7=\"\nval3\" #",
"ASSERTVAR8=\"val3\n\"",
"ASSERTVAR9=\"\n\n\"",
];
self::assertSame($expected, Lines::process($result->success()->get()));
}
public function testProcessQuotes()
{
$content = \file_get_contents(\dirname(\dirname(__DIR__)).'/fixtures/env/multiline.env');
self::assertIsString($content);
$result = Regex::split("/(\r\n|\n|\r)/", $content);
self::assertTrue($result->success()->isDefined());
$expected = [
"TEST=\"test\n test\\\"test\\\"\n test\"",
'TEST_ND="test\\ntest"',
'TEST_NS=\'test\\ntest\'',
'TEST_EQD="https://vision.googleapis.com/v1/images:annotate?key="',
'TEST_EQS=\'https://vision.googleapis.com/v1/images:annotate?key=\'',
"BASE64_ENCODED_MULTILINE=\"qS1zCzMVVUJWQShokv6YVYi+ruKSC/bHV7GmEiyVkLaBWJHNVHCHsgTksEBsy8wJ\nuwycAvR07ZyOJJed4XTRMKnKp1/v+6UATpWzkIjZXytK+pD+XlZimUHTx3uiDcmU\njhQX1wWSxHDqrSWxeIJiTD+BuUyId8FzmXQ3TcBydJ474tmOU2F492ubk3LAiZ18\nmhiRGoshXAOSbS/P3+RZi4bDeNE/No4=\"",
];
self::assertSame($expected, Lines::process($result->success()->get()));
}
}
================================================
FILE: tests/Dotenv/Parser/ParserTest.php
================================================
parse("FOO=BAR\nFOO\nFOO=\"BAR \n\"\nFOO=\"\\n\"");
self::assertIsArray($result);
self::assertCount(4, $result);
$this->checkPositiveEntry($result[0], 'FOO', 'BAR');
$this->checkEmptyEntry($result[1], 'FOO');
$this->checkPositiveEntry($result[2], 'FOO', "BAR \n");
$this->checkPositiveEntry($result[3], 'FOO', "\n");
}
public function testBadEscapeParse()
{
$this->expectException(InvalidFileException::class);
$this->expectExceptionMessage('Failed to parse dotenv file. Encountered an unexpected escape sequence at ["\q"].');
(new Parser())->parse('FOO="\q"');
}
public function testParseInvalidSpaces()
{
$this->expectException(InvalidFileException::class);
$this->expectExceptionMessage('Failed to parse dotenv file. Encountered unexpected whitespace at [bar baz].');
(new Parser())->parse("FOO=bar baz\n");
}
public function testParseStrayEquals()
{
$this->expectException(InvalidFileException::class);
$this->expectExceptionMessage('Failed to parse dotenv file. Encountered an unexpected equals at [=].');
(new Parser())->parse("=\n");
}
public function testParseInvalidName()
{
$this->expectException(InvalidFileException::class);
$this->expectExceptionMessage('Failed to parse dotenv file. Encountered an invalid name at [FOO_ASD!].');
(new Parser())->parse('FOO_ASD!=BAZ');
}
/**
* @param \Dotenv\Parser\Entry $entry
* @param string $name
* @param string $chars
* @param int[] $vars
*
* @return void
*/
private function checkPositiveEntry(Entry $entry, string $name, string $chars, array $vars = [])
{
self::assertInstanceOf(Entry::class, $entry);
self::assertSame($name, $entry->getName());
self::assertTrue($entry->getValue()->isDefined());
$value = $entry->getValue()->get();
self::assertInstanceOf(Value::class, $value);
self::assertSame($chars, $value->getChars());
self::assertSame($vars, $value->getVars());
}
/**
* @param \Dotenv\Parser\Entry $entry
* @param string $name
*
* @return void
*/
private function checkEmptyEntry(Entry $entry, string $name)
{
self::assertInstanceOf(Entry::class, $entry);
self::assertSame('FOO', $entry->getName());
self::assertFalse($entry->getValue()->isDefined());
}
}
================================================
FILE: tests/Dotenv/Repository/Adapter/ArrayAdapterTest.php
================================================
write('CONST_TEST', 'foo bar baz');
$value = $adapter->read('CONST_TEST');
self::assertTrue($value->isDefined());
self::assertSame('foo bar baz', $value->get());
}
public function testUndefinedRead()
{
$adapter = self::createAdapter();
unset($_ENV['CONST_TEST']);
$value = $adapter->read('CONST_TEST');
self::assertFalse($value->isDefined());
}
public function testGoodWrite()
{
$adapter = self::createAdapter();
self::assertTrue($adapter->write('CONST_TEST', 'foo'));
self::assertSame('foo', $adapter->read('CONST_TEST')->get());
}
public function testEmptyWrite()
{
$adapter = self::createAdapter();
self::assertTrue($adapter->write('CONST_TEST', ''));
self::assertSame('', $adapter->read('CONST_TEST')->get());
}
public function testGoodDelete()
{
$adapter = self::createAdapter();
self::assertTrue($adapter->delete('CONST_TEST'));
self::assertFalse($adapter->read('CONST_TEST')->isDefined());
}
/**
* @return \Dotenv\Repository\Adapter\AdapterInterface
*/
private static function createAdapter()
{
return ArrayAdapter::create()->get();
}
}
================================================
FILE: tests/Dotenv/Repository/Adapter/EnvConstAdapterTest.php
================================================
read('CONST_TEST');
self::assertTrue($value->isDefined());
self::assertSame('foo bar baz', $value->get());
}
public function testFalseRead()
{
$_ENV['CONST_TEST'] = false;
$value = self::createAdapter()->read('CONST_TEST');
self::assertTrue($value->isDefined());
self::assertSame('false', $value->get());
}
public function testTrueRead()
{
$_ENV['CONST_TEST'] = true;
$value = self::createAdapter()->read('CONST_TEST');
self::assertTrue($value->isDefined());
self::assertSame('true', $value->get());
}
public function testBadTypeRead()
{
$_ENV['CONST_TEST'] = [123];
$value = self::createAdapter()->read('CONST_TEST');
self::assertFalse($value->isDefined());
}
public function testUndefinedRead()
{
unset($_ENV['CONST_TEST']);
$value = self::createAdapter()->read('CONST_TEST');
self::assertFalse($value->isDefined());
}
public function testGoodWrite()
{
self::assertTrue(self::createAdapter()->write('CONST_TEST', 'foo'));
self::assertSame('foo', $_ENV['CONST_TEST']);
}
public function testEmptyWrite()
{
self::assertTrue(self::createAdapter()->write('CONST_TEST', ''));
self::assertSame('', $_ENV['CONST_TEST']);
}
public function testGoodDelete()
{
self::assertTrue(self::createAdapter()->delete('CONST_TEST'));
self::assertFalse(isset($_ENV['CONST_TEST']));
}
/**
* @return \Dotenv\Repository\Adapter\AdapterInterface
*/
private static function createAdapter()
{
return EnvConstAdapter::create()->get();
}
}
================================================
FILE: tests/Dotenv/Repository/Adapter/PutenvAdapterTest.php
================================================
read('CONST_TEST');
self::assertTrue($value->isDefined());
self::assertSame('foo bar baz', $value->get());
}
public function testUndefinedRead()
{
\putenv('CONST_TEST');
$value = self::createAdapter()->read('CONST_TEST');
self::assertFalse($value->isDefined());
}
public function testGoodWrite()
{
self::assertTrue(self::createAdapter()->write('CONST_TEST', 'foo'));
self::assertSame('foo', \getenv('CONST_TEST'));
}
public function testEmptyWrite()
{
self::assertTrue(self::createAdapter()->write('CONST_TEST', ''));
self::assertSame('', \getenv('CONST_TEST'));
}
public function testGoodDelete()
{
self::assertTrue(self::createAdapter()->delete('CONST_TEST'));
self::assertFalse(\getenv('CONST_TEST'));
}
/**
* @return \Dotenv\Repository\Adapter\AdapterInterface
*/
private static function createAdapter()
{
return PutenvAdapter::create()->get();
}
}
================================================
FILE: tests/Dotenv/Repository/Adapter/ServerConstAdapterTest.php
================================================
read('CONST_TEST');
self::assertTrue($value->isDefined());
self::assertSame('foo bar baz', $value->get());
}
public function testFalseRead()
{
$_SERVER['CONST_TEST'] = false;
$value = self::createAdapter()->read('CONST_TEST');
self::assertTrue($value->isDefined());
self::assertSame('false', $value->get());
}
public function testTrueRead()
{
$_SERVER['CONST_TEST'] = true;
$value = self::createAdapter()->read('CONST_TEST');
self::assertTrue($value->isDefined());
self::assertSame('true', $value->get());
}
public function testBadTypeRead()
{
$_SERVER['CONST_TEST'] = [123];
$value = self::createAdapter()->read('CONST_TEST');
self::assertFalse($value->isDefined());
}
public function testUndefinedRead()
{
unset($_SERVER['CONST_TEST']);
$value = self::createAdapter()->read('CONST_TEST');
self::assertFalse($value->isDefined());
}
public function testGoodWrite()
{
self::assertTrue(self::createAdapter()->write('CONST_TEST', 'foo'));
self::assertSame('foo', $_SERVER['CONST_TEST']);
}
public function testEmptyWrite()
{
self::assertTrue(self::createAdapter()->write('CONST_TEST', ''));
self::assertSame('', $_SERVER['CONST_TEST']);
}
public function testGoodDelete()
{
self::assertTrue(self::createAdapter()->delete('CONST_TEST'));
self::assertFalse(isset($_SERVER['CONST_TEST']));
}
/**
* @return \Dotenv\Repository\Adapter\AdapterInterface
*/
private static function createAdapter()
{
return ServerConstAdapter::create()->get();
}
}
================================================
FILE: tests/Dotenv/Repository/RepositoryTest.php
================================================
|null
*/
private $keyVal;
/**
* @before
*
* @return void
*/
public function refreshKeyVal()
{
$this->keyVal(true);
}
/**
* @return void
*/
private function load()
{
Dotenv::createMutable(\dirname(\dirname(__DIR__)).'/fixtures/env')->load();
}
/**
* Generates a new key/value pair or returns the previous one.
*
* Since most of our functionality revolves around setting/retrieving keys
* and values, we have this utility function to help generate new, unique
* key/value pairs.
*
* @param bool $reset
*
* @return array
*/
private function keyVal(bool $reset = false)
{
if (!isset($this->keyVal) || $reset) {
$this->keyVal = [\uniqid() => \uniqid()];
}
return $this->keyVal;
}
/**
* Returns the key from keyVal(), without reset.
*
* @return string
*/
private function key()
{
$keyVal = $this->keyVal();
return (string) \key($keyVal);
}
/**
* Returns the value from keyVal(), without reset.
*
* @return string
*/
private function value()
{
$keyVal = $this->keyVal();
/** @var string */
return \reset($keyVal);
}
public function testRepositoryInstanceOf()
{
self::assertInstanceOf(RepositoryInterface::class, RepositoryBuilder::createWithNoAdapters()->make());
self::assertInstanceOf(RepositoryInterface::class, RepositoryBuilder::createWithDefaultAdapters()->make());
}
public function testMutableLoaderClearsEnvironmentVars()
{
$repository = RepositoryBuilder::createWithDefaultAdapters()->make();
// Set an environment variable.
$repository->set($this->key(), $this->value());
// Clear the set environment variable.
$repository->clear($this->key());
self::assertNull($repository->get($this->key()));
self::assertFalse(\getenv($this->key()));
self::assertFalse(isset($_ENV[$this->key()]));
self::assertFalse(isset($_SERVER[$this->key()]));
}
public function testImmutableLoaderCannotClearExistingEnvironmentVars()
{
$this->load();
$repository = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make();
// Pre-set an environment variable.
RepositoryBuilder::createWithDefaultAdapters()->make()->set($this->key(), $this->value());
// Attempt to clear the environment variable, check that it fails.
$repository->clear($this->key());
self::assertSame($this->value(), $repository->get($this->key()));
self::assertTrue(isset($_ENV[$this->key()]));
self::assertTrue(isset($_SERVER[$this->key()]));
}
public function testImmutableLoaderCanClearSetEnvironmentVars()
{
$this->load();
$repository = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make();
// Set an environment variable.
$repository->set($this->key(), $this->value());
// Attempt to clear the environment variable, check that it works.
$repository->clear($this->key());
self::assertNull($repository->get($this->key()));
self::assertFalse(\getenv($this->key()));
self::assertFalse(isset($_ENV[$this->key()]));
self::assertFalse(isset($_SERVER[$this->key()]));
}
public function testCheckingWhetherVariableExists()
{
$this->load();
$repo = RepositoryBuilder::createWithDefaultAdapters()->make();
self::assertTrue($repo->has('FOO'));
self::assertFalse($repo->has('NON_EXISTING_VARIABLE'));
}
public function testHasWithBadVariable()
{
$repo = RepositoryBuilder::createWithDefaultAdapters()->make();
$this->expectException(TypeError::class);
$repo->has(null);
}
public function testGettingVariableByName()
{
$this->load();
$repo = RepositoryBuilder::createWithDefaultAdapters()->make();
self::assertSame('bar', $repo->get('FOO'));
}
public function testGettingNullVariable()
{
$repo = RepositoryBuilder::createWithDefaultAdapters()->make();
$this->expectException(TypeError::class);
$repo->get(null);
}
public function testGettingEmptyVariable()
{
$repo = RepositoryBuilder::createWithDefaultAdapters()->make();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Expected name to be a non-empty string.');
$repo->get('');
}
public function testSettingVariable()
{
$this->load();
$repo = RepositoryBuilder::createWithDefaultAdapters()->make();
self::assertSame('bar', $repo->get('FOO'));
$repo->set('FOO', 'new');
self::assertSame('new', $repo->get('FOO'));
}
public function testSettingNullVariable()
{
$repo = RepositoryBuilder::createWithDefaultAdapters()->make();
$this->expectException(TypeError::class);
$repo->set(null, 'foo');
}
public function testSettingEmptyVariable()
{
$repo = RepositoryBuilder::createWithDefaultAdapters()->make();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Expected name to be a non-empty string.');
$repo->set('', 'foo');
}
public function testClearingVariable()
{
$this->load();
$repo = RepositoryBuilder::createWithDefaultAdapters()->make();
self::assertTrue($repo->has('FOO'));
$repo->clear('FOO');
self::assertFalse($repo->has('FOO'));
}
public function testClearingVariableWithArrayAdapter()
{
$adapter = ArrayAdapter::create()->get();
$repo = RepositoryBuilder::createWithNoAdapters()->addReader($adapter)->addWriter($adapter)->make();
self::assertFalse($repo->has('FOO'));
$repo->set('FOO', 'BAR');
self::assertTrue($repo->has('FOO'));
$repo->clear('FOO');
self::assertFalse($repo->has('FOO'));
}
public function testClearingNullVariable()
{
$repo = RepositoryBuilder::createWithDefaultAdapters()->make();
$this->expectException(TypeError::class);
$repo->clear(null);
}
public function testClearingEmptyVariable()
{
$repo = RepositoryBuilder::createWithDefaultAdapters()->make();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Expected name to be a non-empty string.');
$repo->clear('');
}
public function testCannotSetVariableOnImmutableInstance()
{
$this->load();
$repo = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make();
self::assertSame('bar', $repo->get('FOO'));
$repo->set('FOO', 'new');
self::assertSame('bar', $repo->get('FOO'));
}
public function testCannotClearVariableOnImmutableInstance()
{
$this->load();
$repo = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make();
$repo->clear('FOO');
self::assertTrue($repo->has('FOO'));
}
public function testBuildWithBadReader()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Expected either an instance of ');
RepositoryBuilder::createWithNoAdapters()->addReader('123');
}
public function testBuildWithBadWriter()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Expected either an instance of ');
RepositoryBuilder::createWithNoAdapters()->addWriter('123');
}
public function testBuildWithBadAdapter()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Expected either an instance of ');
RepositoryBuilder::createWithNoAdapters()->addAdapter('');
}
}
================================================
FILE: tests/Dotenv/Store/StoreTest.php
================================================
"FOO=bar\nBAR=baz\nSPACED=\"with spaces\"\n\nNULL=\n",
],
Reader::read(
Paths::filePaths([self::$folder], ['.env'])
)
);
}
public function testBasicRead()
{
$builder = StoreBuilder::createWithDefaultName()
->addPath(self::$folder);
self::assertSame(
"FOO=bar\nBAR=baz\nSPACED=\"with spaces\"\n\nNULL=\n",
$builder->make()->read()
);
}
public function testBasicReadWindowsEncoding()
{
$builder = StoreBuilder::createWithNoNames()
->addPath(self::$folder)
->addName('windows.env')
->fileEncoding('Windows-1252');
self::assertSame(
"MBW=\"ñá\"\n",
$builder->make()->read()
);
}
public function testBasicReadBadEncoding()
{
$builder = StoreBuilder::createWithNoNames()
->addPath(self::$folder)
->addName('windows.env')
->fileEncoding('Windowss-1252');
$this->expectException(InvalidEncodingException::class);
$this->expectExceptionMessage('Illegal character encoding [Windowss-1252] specified.');
$builder->make()->read();
}
public function testFileReadMultipleShortCircuitModeDirect()
{
self::assertSame(
[
self::$folder.\DIRECTORY_SEPARATOR.'.env' => "FOO=bar\nBAR=baz\nSPACED=\"with spaces\"\n\nNULL=\n",
],
Reader::read(
Paths::filePaths([self::$folder], ['.env', 'example.env'])
)
);
}
public function testFileReadMultipleShortCircuitMode()
{
$builder = StoreBuilder::createWithNoNames()
->addPath(self::$folder)
->addName('.env')
->addName('example.env')
->shortCircuit();
self::assertSame(
"FOO=bar\nBAR=baz\nSPACED=\"with spaces\"\n\nNULL=\n",
$builder->make()->read()
);
}
public function testFileReadMultipleWithoutShortCircuitModeDirect()
{
self::assertSame(
[
self::$folder.\DIRECTORY_SEPARATOR.'.env' => "FOO=bar\nBAR=baz\nSPACED=\"with spaces\"\n\nNULL=\n",
self::$folder.\DIRECTORY_SEPARATOR.'example.env' => "EG=\"example\"\n",
],
Reader::read(
Paths::filePaths([self::$folder], ['.env', 'example.env']),
false
)
);
}
public function testFileReadMultipleWithoutShortCircuitMode()
{
$builder = StoreBuilder::createWithDefaultName()
->addPath(self::$folder)
->addName('example.env');
self::assertSame(
"FOO=bar\nBAR=baz\nSPACED=\"with spaces\"\n\nNULL=\n\nEG=\"example\"\n",
$builder->make()->read()
);
}
public function testFileReadWithUtf8WithBomEncoding()
{
self::assertSame(
[
self::$folder.\DIRECTORY_SEPARATOR.'utf8-with-bom-encoding.env' => "FOO=bar\nBAR=baz\nSPACED=\"with spaces\"\n",
],
Reader::read(
Paths::filePaths([self::$folder], ['utf8-with-bom-encoding.env'])
)
);
}
}
================================================
FILE: tests/Dotenv/ValidatorTest.php
================================================
addAdapter(ArrayAdapter::class)->make();
return [$repository, Dotenv::create($repository, self::$folder, $name)];
}
/**
* @doesNotPerformAssertions
*/
public function testDotenvRequiredStringEnvironmentVars()
{
$dotenv = self::createArrayDotenv()[1];
$dotenv->load();
$dotenv->required('FOO');
}
/**
* @doesNotPerformAssertions
*/
public function testDotenvAllowedValues()
{
$dotenv = self::createArrayDotenv()[1];
$dotenv->load();
$dotenv->required('FOO')->allowedValues(['bar', 'baz']);
}
/**
* @doesNotPerformAssertions
*/
public function testDotenvAllowedValuesIfPresent()
{
$dotenv = self::createArrayDotenv()[1];
$dotenv->load();
$dotenv->ifPresent('FOO')->allowedValues(['bar', 'baz']);
}
/**
* @doesNotPerformAssertions
*/
public function testDotenvAllowedValuesIfNotPresent()
{
$dotenv = self::createArrayDotenv()[1];
$dotenv->load();
$dotenv->ifPresent('FOOQWERTYOOOOOO')->allowedValues(['bar', 'baz']);
}
public function testDotenvProhibitedValues()
{
$dotenv = self::createArrayDotenv()[1];
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: FOO is not one of [buzz, buz].');
$dotenv->required('FOO')->allowedValues(['buzz', 'buz']);
}
public function testDotenvProhibitedValuesIfPresent()
{
$dotenv = self::createArrayDotenv()[1];
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: FOO is not one of [buzz, buz].');
$dotenv->ifPresent('FOO')->allowedValues(['buzz', 'buz']);
}
public function testDotenvRequiredThrowsRuntimeException()
{
[$repo, $dotenv] = self::createArrayDotenv();
$dotenv->load();
self::assertFalse($repo->has('FOOX'));
self::assertFalse($repo->has('NOPE'));
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: FOOX is missing, NOPE is missing.');
$dotenv->required(['FOOX', 'NOPE']);
}
/**
* @doesNotPerformAssertions
*/
public function testDotenvRequiredArrayEnvironmentVars()
{
$dotenv = self::createArrayDotenv()[1];
$dotenv->load();
$dotenv->required(['FOO', 'BAR']);
}
public function testDotenvAssertions()
{
[$repo, $dotenv] = self::createArrayDotenv('assertions.env');
$dotenv->load();
self::assertSame('val1', $repo->get('ASSERTVAR1'));
self::assertSame('', $repo->get('ASSERTVAR2'));
self::assertSame('val3 ', $repo->get('ASSERTVAR3'));
self::assertSame('0', $repo->get('ASSERTVAR4'));
self::assertSame('#foo', $repo->get('ASSERTVAR5'));
self::assertSame("val1\nval2", $repo->get('ASSERTVAR6'));
self::assertSame("\nval3", $repo->get('ASSERTVAR7'));
self::assertSame("val3\n", $repo->get('ASSERTVAR8'));
$dotenv->required([
'ASSERTVAR1',
'ASSERTVAR2',
'ASSERTVAR3',
'ASSERTVAR4',
'ASSERTVAR5',
'ASSERTVAR6',
'ASSERTVAR7',
'ASSERTVAR8',
'ASSERTVAR9',
]);
$dotenv->required([
'ASSERTVAR1',
'ASSERTVAR3',
'ASSERTVAR4',
'ASSERTVAR5',
'ASSERTVAR6',
'ASSERTVAR7',
'ASSERTVAR8',
])->notEmpty();
$dotenv->required([
'ASSERTVAR1',
'ASSERTVAR4',
'ASSERTVAR5',
])->notEmpty()->allowedValues(['0', 'val1', '#foo']);
}
public function testDotenvEmptyThrowsRuntimeException()
{
$dotenv = self::createArrayDotenv('assertions.env')[1];
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: ASSERTVAR2 is empty.');
$dotenv->required('ASSERTVAR2')->notEmpty();
}
/**
* @doesNotPerformAssertions
*/
public function testDotenvEmptyWhenNotPresent()
{
$dotenv = self::createArrayDotenv('assertions.env')[1];
$dotenv->load();
$dotenv->ifPresent('ASSERTVAR2_NO_SUCH_VARIABLE')->notEmpty();
}
public function testDotenvStringOfSpacesConsideredEmpty()
{
$dotenv = self::createArrayDotenv('assertions.env')[1];
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: ASSERTVAR9 is empty.');
$dotenv->required('ASSERTVAR9')->notEmpty();
}
/**
* List of valid boolean values in fixtures/env/booleans.env.
*
* @return string[][]
*/
public static function validBooleanValuesDataProvider()
{
return [
['VALID_EXPLICIT_LOWERCASE_TRUE'],
['VALID_EXPLICIT_LOWERCASE_FALSE'],
['VALID_EXPLICIT_UPPERCASE_TRUE'],
['VALID_EXPLICIT_UPPERCASE_FALSE'],
['VALID_EXPLICIT_MIXEDCASE_TRUE'],
['VALID_EXPLICIT_MIXEDCASE_FALSE'],
['VALID_NUMBER_TRUE'],
['VALID_NUMBER_FALSE'],
['VALID_ONOFF_LOWERCASE_TRUE'],
['VALID_ONOFF_LOWERCASE_FALSE'],
['VALID_ONOFF_UPPERCASE_TRUE'],
['VALID_ONOFF_UPPERCASE_FALSE'],
['VALID_ONOFF_MIXEDCASE_TRUE'],
['VALID_ONOFF_MIXEDCASE_FALSE'],
['VALID_YESNO_LOWERCASE_TRUE'],
['VALID_YESNO_LOWERCASE_FALSE'],
['VALID_YESNO_UPPERCASE_TRUE'],
['VALID_YESNO_UPPERCASE_FALSE'],
['VALID_YESNO_MIXEDCASE_TRUE'],
['VALID_YESNO_MIXEDCASE_FALSE'],
];
}
/**
* @dataProvider validBooleanValuesDataProvider
* @doesNotPerformAssertions
*/
public function testCanValidateBooleans(string $boolean)
{
$dotenv = Dotenv::createImmutable(self::$folder, 'booleans.env');
$dotenv->load();
$dotenv->required($boolean)->isBoolean();
}
/**
* @dataProvider validBooleanValuesDataProvider
* @doesNotPerformAssertions
*/
public function testCanValidateBooleansIfPresent(string $boolean)
{
$dotenv = Dotenv::createImmutable(self::$folder, 'booleans.env');
$dotenv->load();
$dotenv->ifPresent($boolean)->isBoolean();
}
/**
* List of non-boolean values in fixtures/env/booleans.env.
*
* @return string[][]
*/
public static function invalidBooleanValuesDataProvider()
{
return [
['INVALID_SOMETHING'],
['INVALID_EMPTY'],
['INVALID_EMPTY_STRING'],
['INVALID_NULL'],
['INVALID_NUMBER_POSITIVE'],
['INVALID_NUMBER_NEGATIVE'],
['INVALID_MINUS'],
['INVALID_TILDA'],
['INVALID_EXCLAMATION'],
];
}
/**
* @dataProvider invalidBooleanValuesDataProvider
*/
public function testCanInvalidateNonBooleans(string $boolean)
{
$dotenv = Dotenv::createImmutable(self::$folder, 'booleans.env');
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: INVALID_');
$dotenv->required($boolean)->isBoolean();
}
/**
* @dataProvider invalidBooleanValuesDataProvider
*/
public function testCanInvalidateNonBooleansIfPresent(string $boolean)
{
$dotenv = Dotenv::createImmutable(self::$folder, 'booleans.env');
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: INVALID_');
$dotenv->ifPresent($boolean)->isBoolean();
}
public function testCanInvalidateBooleanNonExist()
{
$dotenv = Dotenv::createImmutable(self::$folder, 'booleans.env');
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: VAR_DOES_NOT_EXIST_234782462764');
$dotenv->required(['VAR_DOES_NOT_EXIST_234782462764'])->isBoolean();
}
/**
* @doesNotPerformAssertions
*/
public function testIfPresentBooleanNonExist()
{
$dotenv = Dotenv::createImmutable(self::$folder, 'booleans.env');
$dotenv->load();
$dotenv->ifPresent(['VAR_DOES_NOT_EXIST_234782462764'])->isBoolean();
}
/**
* List of valid integer values in fixtures/env/integers.env.
*
* @return string[][]
*/
public static function validIntegerValuesDataProvider()
{
return [
['VALID_ZERO'],
['VALID_ONE'],
['VALID_TWO'],
['VALID_LARGE'],
['VALID_HUGE'],
];
}
/**
* @dataProvider validIntegerValuesDataProvider
* @doesNotPerformAssertions
*/
public function testCanValidateIntegers(string $integer)
{
$dotenv = Dotenv::createImmutable(self::$folder, 'integers.env');
$dotenv->load();
$dotenv->required($integer)->isInteger();
}
/**
* @dataProvider validIntegerValuesDataProvider
* @doesNotPerformAssertions
*/
public function testCanValidateIntegersIfPresent(string $integer)
{
$dotenv = Dotenv::createImmutable(self::$folder, 'integers.env');
$dotenv->load();
$dotenv->ifPresent($integer)->isInteger();
}
/**
* List of non-integer values in fixtures/env/integers.env.
*
* @return string[][]
*/
public static function invalidIntegerValuesDataProvider()
{
return [
['INVALID_SOMETHING'],
['INVALID_EMPTY'],
['INVALID_EMPTY_STRING'],
['INVALID_NULL'],
['INVALID_NEGATIVE'],
['INVALID_MINUS'],
['INVALID_TILDA'],
['INVALID_EXCLAMATION'],
['INVALID_SPACES'],
['INVALID_COMMAS'],
];
}
/**
* @dataProvider invalidIntegerValuesDataProvider
*/
public function testCanInvalidateNonIntegers(string $integer)
{
$dotenv = Dotenv::createImmutable(self::$folder, 'integers.env');
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: INVALID_');
$dotenv->required($integer)->isInteger();
}
/**
* @dataProvider invalidIntegerValuesDataProvider
*/
public function testCanInvalidateNonIntegersIfExist(string $integer)
{
$dotenv = Dotenv::createImmutable(self::$folder, 'integers.env');
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: INVALID_');
$dotenv->ifPresent($integer)->isInteger();
}
public function testCanInvalidateIntegerNonExist()
{
$dotenv = Dotenv::createImmutable(self::$folder, 'integers.env');
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: VAR_DOES_NOT_EXIST_234782462764');
$dotenv->required(['VAR_DOES_NOT_EXIST_234782462764'])->isInteger();
}
/**
* @doesNotPerformAssertions
*/
public function testIfPresentIntegerNonExist()
{
$dotenv = Dotenv::createImmutable(self::$folder, 'integers.env');
$dotenv->load();
$dotenv->ifPresent(['VAR_DOES_NOT_EXIST_234782462764'])->isInteger();
}
/**
* @doesNotPerformAssertions
*/
public function testDotenvRegexMatchPass()
{
$dotenv = Dotenv::createImmutable(self::$folder);
$dotenv->load();
$dotenv->required('FOO')->allowedRegexValues('([[:lower:]]{3})');
}
public function testDotenvRegexMatchFail()
{
$dotenv = Dotenv::createImmutable(self::$folder);
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: FOO does not match "/^([[:lower:]]{1})$/".');
$dotenv->required('FOO')->allowedRegexValues('/^([[:lower:]]{1})$/');
}
public function testDotenvRegexMatchError()
{
$dotenv = Dotenv::createImmutable(self::$folder);
$dotenv->load();
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('One or more environment variables failed assertions: FOO does not match "/([[:lower:]{1{".');
$dotenv->required('FOO')->allowedRegexValues('/([[:lower:]{1{');
}
/**
* @doesNotPerformAssertions
*/
public function testDotenvRegexMatchNotPresent()
{
$dotenv = Dotenv::createImmutable(self::$folder);
$dotenv->load();
$dotenv->ifPresent('FOOOOOOOOOOO')->allowedRegexValues('([[:lower:]]{3})');
}
}
================================================
FILE: tests/fixtures/env/assertions.env
================================================
ASSERTVAR1=val1
ASSERTVAR2=""
ASSERTVAR3="val3 "
ASSERTVAR4="0" # empty looking value
ASSERTVAR5="#foo"
ASSERTVAR6="val1
val2"
ASSERTVAR7="
val3" #
ASSERTVAR8="val3
"
ASSERTVAR9="
"
================================================
FILE: tests/fixtures/env/booleans.env
================================================
VALID_EXPLICIT_LOWERCASE_TRUE=true
VALID_EXPLICIT_LOWERCASE_FALSE=false
VALID_EXPLICIT_UPPERCASE_TRUE=TRUE
VALID_EXPLICIT_UPPERCASE_FALSE=FALSE
VALID_EXPLICIT_MIXEDCASE_TRUE=True
VALID_EXPLICIT_MIXEDCASE_FALSE=False
VALID_NUMBER_TRUE=1
VALID_NUMBER_FALSE=0
VALID_ONOFF_LOWERCASE_TRUE=on
VALID_ONOFF_LOWERCASE_FALSE=off
VALID_ONOFF_UPPERCASE_TRUE=ON
VALID_ONOFF_UPPERCASE_FALSE=OFF
VALID_ONOFF_MIXEDCASE_TRUE=On
VALID_ONOFF_MIXEDCASE_FALSE=Off
VALID_YESNO_LOWERCASE_TRUE=yes
VALID_YESNO_LOWERCASE_FALSE=no
VALID_YESNO_UPPERCASE_TRUE=YES
VALID_YESNO_UPPERCASE_FALSE=NO
VALID_YESNO_MIXEDCASE_TRUE=Yes
VALID_YESNO_MIXEDCASE_FALSE=No
INVALID_SOMETHING=something
INVALID_EMPTY=
INVALID_EMPTY_STRING=""
INVALID_NULL=null
INVALID_NUMBER_POSITIVE=2
INVALID_NUMBER_NEGATIVE=-2
INVALID_MINUS=-
INVALID_TILDA=~
INVALID_EXCLAMATION=!
================================================
FILE: tests/fixtures/env/commented.env
================================================
# This is a comment
CFOO=bar
#CBAR=baz
#CZOO=goo # a comment on a commented row
CSPACED="with spaces" # this is a comment
CQUOTES="a value with a # character" # this is a comment
CQUOTESWITHQUOTE="a value with a # character & a quote \" character inside quotes" # " this is a comment
EMPTY= # comment with empty variable
EMPTY2=# comment with empty variable
FOOO=foo# comment with no space
BOOLEAN=yes # (yes, no)
CNULL=
## this is a comment ##
================================================
FILE: tests/fixtures/env/empty.env
================================================
EMPTY_VAR
================================================
FILE: tests/fixtures/env/example.env
================================================
EG="example"
================================================
FILE: tests/fixtures/env/exported.env
================================================
export EFOO="bar"
export EBAR = "baz"
export ESPACED="with spaces"
export "EDQUOTED" = 123
export 'ESQUOTED' = 456
export ENULL=""
================================================
FILE: tests/fixtures/env/immutable.env
================================================
IMMUTABLE=false
================================================
FILE: tests/fixtures/env/integers.env
================================================
VALID_ZERO=0
VALID_ONE=1
VALID_TWO=2
VALID_LARGE=99999999
VALID_HUGE=99999999999999999999999999999999
INVALID_SOMETHING=something
INVALID_EMPTY=
INVALID_EMPTY_STRING=""
INVALID_NULL=null
INVALID_NEGATIVE=-2
INVALID_MINUS=-
INVALID_TILDA=~
INVALID_EXCLAMATION=!
INVALID_SPACES=" 123"
INVALID_COMMAS="123,123"
================================================
FILE: tests/fixtures/env/large.env
================================================
LARGE='111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'
HUGE='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
================================================
FILE: tests/fixtures/env/multibyte.env
================================================
MB1="Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě"
MB2=行内支付
APP_ENV=🚀
================================================
FILE: tests/fixtures/env/multiline.env
================================================
TEST="test
test\"test\"
test"
TEST_ND="test\ntest"
TEST_NS='test\ntest'
TEST_EQD="https://vision.googleapis.com/v1/images:annotate?key="
TEST_EQS='https://vision.googleapis.com/v1/images:annotate?key='
BASE64_ENCODED_MULTILINE="qS1zCzMVVUJWQShokv6YVYi+ruKSC/bHV7GmEiyVkLaBWJHNVHCHsgTksEBsy8wJ
uwycAvR07ZyOJJed4XTRMKnKp1/v+6UATpWzkIjZXytK+pD+XlZimUHTx3uiDcmU
jhQX1wWSxHDqrSWxeIJiTD+BuUyId8FzmXQ3TcBydJ474tmOU2F492ubk3LAiZ18
mhiRGoshXAOSbS/P3+RZi4bDeNE/No4="
================================================
FILE: tests/fixtures/env/multiple.env
================================================
MULTI1=foo
MULTI2=${MULTI1}
MULTI1=bar
================================================
FILE: tests/fixtures/env/mutable.env
================================================
MUTABLE=true
================================================
FILE: tests/fixtures/env/nested.env
================================================
NVAR1="Hellō"
NVAR2="World!"
NVAR3="{$NVAR1} {$NVAR2}"
NVAR4="${NVAR1} ${NVAR2}"
NVAR5="$NVAR1 {NVAR2}"
N.VAR6="Special Value"
NVAR7="${N.VAR6}"
NVAR8=""
NVAR9="${NVAR8}"
NVAR10="${NVAR888}"
NVAR11="NVAR1"
NVAR12="${${NVAR11}}"
NVAR13='${${NVAR11}}'
NVAR14='${NVAR1} ${NVAR2}'
NVAR15="\${NVAR1} \${NVAR2}"
================================================
FILE: tests/fixtures/env/quoted.env
================================================
QFOO="bar"
QBAR="baz"
QSPACED="with spaces"
QEQUALS="pgsql:host=localhost;dbname=test"
QNULL=""
QWHITESPACE = "no space"
QESCAPED="test some escaped characters like a quote (\") or maybe a backslash (\\)"
QSLASH="iiiiviiiixiiiiviiii\\n"
SQSLASH='iiiiviiiixiiiiviiii\\n'
================================================
FILE: tests/fixtures/env/specialchars.env
================================================
SPVAR1="$a6^C7k%zs+e^.jvjXk"
SPVAR2="?BUty3koaV3%GA*hMAwH}B"
SPVAR3="jdgEB4{QgEC]HL))&GcXxokB+wqoN+j>xkV7K?m$r"
SPVAR4="22222:22#2^{"
SPVAR5="test some escaped characters like a quote \" or maybe a backslash \\" # not escaped
SPVAR6=secret!@#
SPVAR7='secret!@#'
SPVAR8="secret!@#"
================================================
FILE: tests/fixtures/env/unicodevarnames.env
================================================
AlbertÅberg=Skybert
ДатаЗакрытияРасчетногоПериода='2022-04-01T00:00'
================================================
FILE: tests/fixtures/env/utf8-with-bom-encoding.env
================================================
FOO=bar
BAR=baz
SPACED="with spaces"
================================================
FILE: tests/fixtures/env/windows.env
================================================
MBW=""
================================================
FILE: vendor-bin/phpstan/composer.json
================================================
{
"require": {
"php": "^8.5",
"phpstan/phpstan": "2.1.33",
"phpstan/extension-installer": "1.4.3",
"phpstan/phpstan-deprecation-rules": "2.0.3",
"phpstan/phpstan-strict-rules": "2.0.7"
},
"config": {
"preferred-install": "dist",
"allow-plugins": {
"phpstan/extension-installer": true
}
}
}