Skip to content

Commit e61e0a0

Browse files
author
F. Michel
authoredJul 4, 2021
Merge pull request #3 from jaumarar/process-quotes
Added process of quotes
2 parents e6d703b + 1c906db commit e61e0a0

9 files changed

+269
-42
lines changed
 

‎README.md

+43-7
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,58 @@
44
```
55
APP_ENV=dev
66
DATABASE_DNS=mysql:host=localhost;dbname=test;
7-
DATABASE_USER=root
7+
DATABASE_USER="root"
88
DATABASE_PASSWORD=root
9+
MODULE_ENABLED=true
910
```
1011

11-
**How to use ?**
12+
## Load the variables
1213

1314
```php
1415
<?php
1516
use DevCoder\DotEnv;
1617

17-
(new DotEnv(__DIR__ . '/.env'))->load();
18+
$absolutePathToEnvFile = __DIR__ . '/.env';
1819

19-
echo getenv('APP_ENV');
20-
// dev
21-
echo getenv('DATABASE_DNS');
22-
// mysql:host=localhost;dbname=test;
20+
(new DotEnv($absolutePathToEnvFile))->load();
2321
```
22+
23+
# Use them!
24+
```php
25+
/**
26+
* string(33) "mysql:host=localhost;dbname=test;"
27+
*/
28+
var_dump(getenv('DATABASE_DNS'));
29+
30+
/**
31+
* Removes double and single quotes from the variable:
32+
*
33+
* string(4) "root"
34+
*/
35+
var_dump(getenv('DATABASE_USER'));
36+
37+
/**
38+
* Processes booleans as such:
39+
*
40+
* bool(true)
41+
*/
42+
var_dump(getenv('MODULE_ENABLED'));
43+
```
44+
2445
Ideal for small project
46+
2547
Simple and easy!
48+
49+
# Processors
50+
51+
Also the variables are parsed according to the configuration passed as parameter to the constructor. The available processors are:
52+
53+
## BooleanProcessor
54+
55+
``VARIABLE=false`` will be processed to ```bool(false)```
56+
57+
NOTE: ``VARIABLE="true"`` will be processed to ```string(4) "true"```
58+
59+
## QuotedProcessor
60+
61+
``VARIABLE="anything"`` will be processed to ```string(8) "anything"```

‎src/DotEnv.php

+47-28
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,12 @@
22

33
namespace DevCoder;
44

5+
use DevCoder\Processor\AbstractProcessor;
6+
use DevCoder\Processor\BooleanProcessor;
7+
use DevCoder\Processor\QuotedProcessor;
8+
59
class DotEnv
610
{
7-
/**
8-
* Convert true and false to booleans, instead of:
9-
*
10-
* VARIABLE=false -> ['VARIABLE' => 'false']
11-
*
12-
* it will be
13-
*
14-
* VARIABLE=false -> ['VARIABLE' => false]
15-
*
16-
* default = true
17-
*/
18-
const PROCESS_BOOLEANS = 'PROCESS_BOOLEANS';
19-
2011
/**
2112
* The directory where the .env file can be located.
2213
*
@@ -27,26 +18,42 @@ class DotEnv
2718
/**
2819
* Configure the options on which the parsed will act
2920
*
30-
* @var array
21+
* @var string[]
3122
*/
32-
protected $options = [];
23+
protected $processors = [];
3324

34-
public function __construct(string $path, array $options = [])
25+
public function __construct(string $path, array $processors = null)
3526
{
3627
if (!file_exists($path)) {
3728
throw new \InvalidArgumentException(sprintf('%s does not exist', $path));
3829
}
3930

4031
$this->path = $path;
4132

42-
$this->processOptions($options);
33+
$this->setProcessors($processors);
4334
}
4435

45-
private function processOptions(array $options) : void
36+
private function setProcessors(array $processors = null) : DotEnv
4637
{
47-
$this->options = array_merge([
48-
static::PROCESS_BOOLEANS => true
49-
], $options);
38+
/**
39+
* Fill with default processors
40+
*/
41+
if ($processors === null) {
42+
$this->processors = [
43+
BooleanProcessor::class,
44+
QuotedProcessor::class
45+
];
46+
47+
return $this;
48+
}
49+
50+
foreach ($processors as $processor) {
51+
if (is_subclass_of($processor, AbstractProcessor::class)) {
52+
$this->processors[] = $processor;
53+
}
54+
}
55+
56+
return $this;
5057
}
5158

5259
/**
@@ -78,19 +85,31 @@ public function load() : void
7885
}
7986
}
8087

81-
private function processValue(string $value) {
88+
/**
89+
* Process the value with the configured processors
90+
*
91+
* @param string $value The value to process
92+
* @return string|bool
93+
*/
94+
private function processValue(string $value)
95+
{
96+
/**
97+
* First trim spaces and quotes if configured
98+
*/
8299
$trimmedValue = trim($value);
83100

84-
if (!empty($this->options[static::PROCESS_BOOLEANS])) {
85-
$loweredValue = strtolower($trimmedValue);
86-
87-
$isBoolean = in_array($loweredValue, ['true', 'false'], true);
101+
foreach ($this->processors as $processor) {
102+
/** @var AbstractProcessor $processorInstance */
103+
$processorInstance = new $processor($trimmedValue);
88104

89-
if ($isBoolean) {
90-
return $loweredValue === 'true';
105+
if ($processorInstance->canBeProcessed()) {
106+
return $processorInstance->execute();
91107
}
92108
}
93109

110+
/**
111+
* Does not match any processor options, return as is
112+
*/
94113
return $trimmedValue;
95114
}
96115
}

‎src/Processor/AbstractProcessor.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
namespace DevCoder\Processor;
3+
4+
abstract class AbstractProcessor implements IProcessor
5+
{
6+
/**
7+
* The value to process
8+
* @var string
9+
*/
10+
protected $value;
11+
12+
public function __construct(string $value)
13+
{
14+
$this->value = $value;
15+
}
16+
}

‎src/Processor/BooleanProcessor.php

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
namespace DevCoder\Processor;
3+
4+
class BooleanProcessor extends AbstractProcessor
5+
{
6+
public function canBeProcessed(): bool
7+
{
8+
$loweredValue = strtolower($this->value);
9+
10+
return in_array($loweredValue, ['true', 'false'], true);
11+
}
12+
13+
public function execute()
14+
{
15+
return strtolower($this->value) === 'true';
16+
}
17+
}

‎src/Processor/IProcessor.php

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
namespace DevCoder\Processor;
3+
4+
interface IProcessor
5+
{
6+
public function __construct(string $value);
7+
8+
public function canBeProcessed(): bool;
9+
10+
public function execute();
11+
}

‎src/Processor/QuotedProcessor.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace DevCoder\Processor;
4+
5+
class QuotedProcessor extends AbstractProcessor
6+
{
7+
public function canBeProcessed(): bool
8+
{
9+
$wrappedByDoubleQuotes = $this->isWrappedByChar($this->value, '"');
10+
11+
if ($wrappedByDoubleQuotes) {
12+
return true;
13+
}
14+
15+
return $this->isWrappedByChar($this->value, '\'');
16+
}
17+
18+
public function execute()
19+
{
20+
/**
21+
* Since this function is used for the quote removal
22+
* we don't need mb_substr
23+
*/
24+
return substr($this->value, 1, -1);
25+
}
26+
27+
private function isWrappedByChar(string $value, string $char) : bool
28+
{
29+
return $value[0] === $char && $value[-1] === $char;
30+
}
31+
}

‎tests/DotenvTest.php

+94-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
namespace Test\DevCoder;
44

55
use DevCoder\DotEnv;
6+
use DevCoder\Option;
7+
use DevCoder\Processor\BooleanProcessor;
8+
use DevCoder\Processor\QuotedProcessor;
9+
use PHPUnit\Framework\Assert;
610
use PHPUnit\Framework\TestCase;
711

812
class DotenvTest extends TestCase
@@ -23,46 +27,98 @@ public function testLoad() {
2327
$this->assertEquals('password', getenv('DATABASE_PASSWORD'));
2428
$this->assertFalse(getenv('GOOGLE_API'));
2529
$this->assertFalse(getenv('GOOGLE_MANAGER_KEY'));
30+
$this->assertEquals(true, getenv('BOOLEAN_LITERAL'));
31+
$this->assertEquals('true', getenv('BOOLEAN_QUOTED'));
2632

2733
$this->assertEquals('dev', $_ENV['APP_ENV']);
2834
$this->assertEquals('mysql:host=localhost;dbname=test;', $_ENV['DATABASE_DNS']);
2935
$this->assertEquals('root', $_ENV['DATABASE_USER']);
3036
$this->assertEquals('password', $_ENV['DATABASE_PASSWORD']);
3137
$this->assertFalse(array_key_exists('GOOGLE_API', $_ENV));
3238
$this->assertFalse(array_key_exists('GOOGLE_MANAGER_KEY', $_ENV));
39+
$this->assertEquals(true, $_ENV['BOOLEAN_LITERAL']);
40+
$this->assertEquals('true', $_ENV['BOOLEAN_QUOTED']);
3341

3442
$this->assertEquals('dev', $_SERVER['APP_ENV']);
3543
$this->assertEquals('mysql:host=localhost;dbname=test;', $_SERVER['DATABASE_DNS']);
3644
$this->assertEquals('root', $_SERVER['DATABASE_USER']);
3745
$this->assertEquals('password', $_SERVER['DATABASE_PASSWORD']);
3846
$this->assertFalse(array_key_exists('GOOGLE_API', $_SERVER));
3947
$this->assertFalse(array_key_exists('GOOGLE_MANAGER_KEY', $_SERVER));
48+
$this->assertEquals(true, $_SERVER['BOOLEAN_LITERAL']);
49+
$this->assertEquals('true', $_SERVER['BOOLEAN_QUOTED']);
4050
}
4151

4252
public function testFileNotExist() {
4353
$this->expectException(\InvalidArgumentException::class);
4454
(new DotEnv($this->env('.env.not-exists')))->load();
4555
}
4656

57+
public function testUncompatibleProcessors() {
58+
$this->assertProcessors(
59+
[],
60+
[]
61+
);
62+
63+
$this->assertProcessors(
64+
null,
65+
[BooleanProcessor::class, QuotedProcessor::class]
66+
);
67+
68+
$this->assertProcessors(
69+
[null],
70+
[]
71+
);
72+
73+
$this->assertProcessors(
74+
[new \stdClass()],
75+
[]
76+
);
77+
78+
$this->assertProcessors(
79+
[QuotedProcessor::class, null],
80+
[QuotedProcessor::class]
81+
);
82+
}
83+
84+
private function assertProcessors(array $processorsToUse = null, array $expectedProcessors = [])
85+
{
86+
$dotEnv = new DotEnv($this->env('.env.default'), $processorsToUse);
87+
$dotEnv->load();
88+
89+
$this->assertEquals(
90+
$expectedProcessors,
91+
$this->getProtectedProperty($dotEnv, 'processors')
92+
);
93+
}
94+
95+
private function getProtectedProperty(object $object, string $property) {
96+
$reflection = new \ReflectionClass($object);
97+
$reflectionProperty = $reflection->getProperty($property);
98+
$reflectionProperty->setAccessible(true);
99+
100+
return $reflectionProperty->getValue($object);
101+
}
102+
47103
/**
48104
* @runInSeparateProcess
49105
*/
50106
public function testProcessBoolean()
51107
{
52108
(new DotEnv($this->env('.env.boolean'), [
53-
DotEnv::PROCESS_BOOLEANS => true
109+
BooleanProcessor::class
54110
]))->load();
55111

56112
$this->assertEquals(false, $_ENV['FALSE1']);
57113
$this->assertEquals(false, $_ENV['FALSE2']);
58114
$this->assertEquals(false, $_ENV['FALSE3']);
59-
$this->assertEquals("'false'", $_ENV['FALSE4']);
115+
$this->assertEquals("'false'", $_ENV['FALSE4']); // Since we don't have the QuotedProcessor::class this will be the result
60116
$this->assertEquals('0', $_ENV['FALSE5']);
61117

62118
$this->assertEquals(true, $_ENV['TRUE1']);
63119
$this->assertEquals(true, $_ENV['TRUE2']);
64120
$this->assertEquals(true, $_ENV['TRUE3']);
65-
$this->assertEquals("'true'", $_ENV['TRUE4']);
121+
$this->assertEquals("'true'", $_ENV['TRUE4']); // Since we don't have the QuotedProcessor::class this will be the result
66122
$this->assertEquals('1', $_ENV['TRUE5']);
67123
}
68124

@@ -71,12 +127,44 @@ public function testProcessBoolean()
71127
*/
72128
public function testDontProcessBoolean()
73129
{
74-
(new DotEnv($this->env('.env.boolean'), [
75-
DotEnv::PROCESS_BOOLEANS => false
76-
]))->load();
130+
(new DotEnv($this->env('.env.boolean'), []))->load();
77131

78132
$this->assertEquals('false', $_ENV['FALSE1']);
79133

80134
$this->assertEquals('true', $_ENV['TRUE1']);
81135
}
136+
137+
/**
138+
* @runInSeparateProcess
139+
*/
140+
public function testProcessQuotes()
141+
{
142+
(new DotEnv($this->env('.env.quotes'), [
143+
QuotedProcessor::class
144+
]))->load();
145+
146+
$this->assertEquals('q1', $_ENV['QUOTED1']);
147+
$this->assertEquals('q2', $_ENV['QUOTED2']);
148+
$this->assertEquals('"q3"', $_ENV['QUOTED3']);
149+
$this->assertEquals('This is a "sample" value', $_ENV['QUOTED4']);
150+
$this->assertEquals('\"This is a "sample" value\"', $_ENV['QUOTED5']);
151+
$this->assertEquals('"q6', $_ENV['QUOTED6']);
152+
$this->assertEquals('q7"', $_ENV['QUOTED7']);
153+
}
154+
155+
/**
156+
* @runInSeparateProcess
157+
*/
158+
public function testDontProcessQuotes()
159+
{
160+
(new DotEnv($this->env('.env.quotes'), []))->load();
161+
162+
$this->assertEquals('"q1"', $_ENV['QUOTED1']);
163+
$this->assertEquals('\'q2\'', $_ENV['QUOTED2']);
164+
$this->assertEquals('""q3""', $_ENV['QUOTED3']);
165+
$this->assertEquals('"This is a "sample" value"', $_ENV['QUOTED4']);
166+
$this->assertEquals('\"This is a "sample" value\"', $_ENV['QUOTED5']);
167+
$this->assertEquals('"q6', $_ENV['QUOTED6']);
168+
$this->assertEquals('q7"', $_ENV['QUOTED7']);
169+
}
82170
}

‎tests/env/.env.default

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ DATABASE_DNS=mysql:host=localhost;dbname=test;
44
DATABASE_USER=root
55
DATABASE_PASSWORD = password
66
#GOOGLE_API=DJfa7czhKaJ0Pig6j9XpSjT6NpXZUZwK
7-
# GOOGLE_MANAGER_KEY=P7RkBUQHIkPUEPy3yCTT4gGYa2DjRth8
7+
# GOOGLE_MANAGER_KEY=P7RkBUQHIkPUEPy3yCTT4gGYa2DjRth8
8+
BOOLEAN_LITERAL=true
9+
BOOLEAN_QUOTED="true"

‎tests/env/.env.quotes

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
QUOTED1="q1"
2+
QUOTED2='q2'
3+
QUOTED3=""q3""
4+
QUOTED4="This is a "sample" value"
5+
QUOTED5=\"This is a "sample" value\"
6+
QUOTED6="q6
7+
QUOTED7=q7"

0 commit comments

Comments
 (0)
Please sign in to comment.