diff --git a/composer.json b/composer.json
index a3885e0..75a2f4a 100644
--- a/composer.json
+++ b/composer.json
@@ -4,8 +4,9 @@
"keywords": [ ],
"minimum-stability": "stable",
- "require": {
- "php": "~7.4 || ~8.0"
+ "require": {
+ "php": "~7.4 || ~8.0",
+ "jrosset/singleton": "^1.0"
},
"autoload": {
"psr-4": {
@@ -30,4 +31,4 @@
"docs": "https://git.jrosset.ovh/jrosset/PhpEnvReader/wiki",
"source": "https://git.jrosset.ovh/jrosset/PhpEnvReader"
}
-}
\ No newline at end of file
+}
diff --git a/composer.lock b/composer.lock
index 80da9e8..e596821 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,50 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "2ab889d9a2d5d5b28b48164a73b529b2",
- "packages": [ ],
+ "content-hash": "2f92e58b7bd7a162f29ef03fe018cec6",
+ "packages": [
+ {
+ "name": "jrosset/singleton",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://git.jrosset.ovh/jrosset/PhpSingleton",
+ "reference": "0fccc3b4b1c46e42656e84e264555e57bf2f27cd"
+ },
+ "require": {
+ "php": "~7.4 || ~8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "jrosset\\": "src/"
+ },
+ "exclude-from-classmap": [
+ "tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "CC-BY-4.0"
+ ],
+ "authors": [
+ {
+ "name": "Julien Rosset",
+ "email": "jul.rosset@gmail.com"
+ }
+ ],
+ "description": "PHP Trait to implements the singleton design pattern",
+ "homepage": "https://git.jrosset.ovh/jrosset/PhpSingleton",
+ "support": {
+ "docs": "https://git.jrosset.ovh/jrosset/PhpSingleton/wiki",
+ "email": "jul.rosset@gmail.com",
+ "issues": "https://git.jrosset.ovh/jrosset/PhpSingleton/issues",
+ "source": "https://git.jrosset.ovh/jrosset/PhpSingleton",
+ "wiki": "https://git.jrosset.ovh/jrosset/PhpSingleton/wiki"
+ },
+ "time": "2021-09-03T10:57:30+00:00"
+ }
+ ],
"packages-dev": [ ],
"aliases": [ ],
"minimum-stability": "stable",
diff --git a/src/EnvReader/Env.php b/src/EnvReader/Env.php
new file mode 100644
index 0000000..8fd1089
--- /dev/null
+++ b/src/EnvReader/Env.php
@@ -0,0 +1,235 @@
+
+ * Overwrite {@see Env::initProperties()} to set initial properties
+ */
+abstract class Env {
+ use TSingleton;
+
+ /**
+ * ENV file path
+ */
+ protected const PATH_ENV = '.env';
+
+ /**
+ * @var string[] Current properties, read from ENV file (<property name> => <property value>)
+ * NOTE: All properties' name are stored in upper-case
+ */
+ private array $properties;
+
+ /**
+ * Initialize initial properties then read ENV file
+ *
+ * @throws Exception If ENV can't be read
+ */
+ protected function __construct () {
+ $this->properties = array_change_key_case($this->initProperties(), CASE_UPPER);
+ $this->readEnv();
+ }
+ /**
+ * Get all properties
+ *
+ * @return string[] Get all properties (properties' name are in upper case)
+ */
+ public function getProperties (): array {
+ return $this->properties;
+ }
+ /**
+ * Check if a property id defined
+ *
+ * @param string $name The property name
+ *
+ * @return bool Is the property defined ?
+ */
+ public function hasProperty (string $name): bool {
+ return array_key_exists(mb_strtoupper($name), $this->getProperties());
+ }
+ /**
+ * Get a property value
+ *
+ * @param string $name The property name
+ * @param string|null $default The default value if property is not set
+ * Raise an exception if property is not set AND $default is Null
+ *
+ * @return string The property value
+ *
+ * @throws UnexpectedValueException If property is not set AND $default is Null
+ */
+ public function getProperty (string $name, ?string $default = null): string {
+ $name = mb_strtoupper($name);
+ if (!$this->hasProperty($name)) {
+ if ($default === null) {
+ throw new UnexpectedValueException('The "' . $name . '" property is not set');
+ }
+ return $default;
+ }
+
+ return $this->properties[$name];
+ }
+ /**
+ * Get a property value as a boolean
+ *
+ * @param string $name The property name
+ * @param bool|null $default The default value if property is not set
+ * Raise an exception if property is not set AND $default is Null
+ *
+ * @return bool The property value
+ *
+ * @throws UnexpectedValueException If property is not set AND $default is Null
+ * @throws RangeException If the property can't be cast to a boolean
+ *
+ * @noinspection PhpUnused
+ */
+ public function getPropertyAsBool (string $name, ?bool $default = null): bool {
+ switch ($value = $this->getProperty($name, $default === null ? null : ($default === true ? '1' : '0'))) {
+ case '1':
+ case 'true':
+ return true;
+
+ case '0':
+ case 'false':
+ return false;
+
+ default:
+ throw new RangeException('The "' . $name . '" property is not a valid boolean : ' . $value);
+ }
+ }
+ /**
+ * Get a property value as an integer
+ *
+ * @param string $name The property name
+ * @param int|null $default The default value if property is not set
+ * Raise an exception if property is not set AND $default is Null
+ *
+ * @return int The property value
+ *
+ * @throws UnexpectedValueException If property is not set AND $default is Null
+ * @throws RangeException If the property can't be cast to an integer
+ *
+ * @noinspection PhpUnused
+ */
+ public function getPropertyAsInt (string $name, ?int $default = null): int {
+ $value = $this->getProperty($name, $default === null ? null : (string)$default);
+ if (preg_match('#^\s*(?[0-9]+)\s*$#', $value, $match) !== 1) {
+ throw new RangeException('The "' . $name . '" property is not a valid integer : ' . $value);
+ }
+ return (int)$match['value'];
+ }
+ /**
+ * Get a property value as a float value (the decimal separator is dot)
+ *
+ * @param string $name The property name
+ * @param float|null $default The default value if property is not set
+ * Raise an exception if property is not set AND $default is Null
+ *
+ * @return float The property value
+ *
+ * @throws UnexpectedValueException If property is not set AND $default is Null
+ * @throws RangeException If the property can't be cast to a float value
+ *
+ * @noinspection PhpUnused
+ */
+ public function getPropertyAsReal (string $name, ?float $default = null): float {
+ $value = $this->getProperty($name, $default === null ? null : (string)$default);
+ if (preg_match('#^\s*(?[0-9]+(?:\.[0-9]+)?)\s*$#', $value, $match) !== 1) {
+ throw new RangeException('The "' . $name . '" property is not a valid float value : ' . $value);
+ }
+ return (float)$match['value'];
+ }
+ /**
+ * Get a property value as a date and time ({@link https://www.php.net/manual/book.datetime.php DateTime})
+ *
+ * The date must respect {@link https://datatracker.ietf.org/doc/html/rfc3339 RFC339} norm
+ *
+ * @param string $name The property name
+ * @param DateTime|null $default The default value if property is not set
+ * Raise an exception if property is not set AND $default is Null
+ *
+ * @return DateTime The property value
+ *
+ * @throws UnexpectedValueException If property is not set AND $default is Null
+ * @throws RangeException If the property can't be cast to a date and time
+ *
+ * @noinspection PhpUnused
+ */
+ public function getPropertyAsDateTime (string $name, ?DateTime $default = null): DateTime {
+ $value = DateTime::createFromFormat(
+ DateTimeInterface::RFC3339,
+ $this->getProperty($name, $default === null ? null : $default->format(DateTimeInterface::RFC3339))
+ );
+
+ $errors = DateTime::getLastErrors();
+ if ($errors !== false) {
+ $messages = [];
+
+ if (($nb_error = $errors['error_count']) > 0) {
+ foreach ($errors['errors'] as $idx => $error) {
+ $messages[] = 'ERREUR #' . $idx . ' : ' . $error;
+ }
+ }
+ if (($nb_warning = $errors['warning_count']) > 0) {
+ foreach ($errors['warnings'] as $idx => $warning) {
+ $messages[] = 'ALERTE #' . $idx . ' : ' . $warning;
+ }
+ }
+
+ if ($nb_error + $nb_warning > 0) {
+ throw new RangeException('The "' . $name . '" property is not a valid date and time : ' . $value . "\n\n" . implode("\n", $messages));
+ }
+ }
+ if ($value === false) {
+ throw new RangeException('The "' . $name . '" property is not a valid date and time : ' . $value);
+ }
+
+ return $value;
+ }
+ /**
+ * Initialize initial properties
+ *
+ * @return string[] Properties initial values (<property name> => <property value>)
+ */
+ protected function initProperties (): array {
+ return [
+ 'APP_DEV' => 0,
+ ];
+ }
+ /**
+ * Read the ENV file
+ *
+ * @throws Exception If ENV can't be read
+ */
+ private function readEnv (): void {
+ if (!file_exists(static::PATH_ENV)) {
+ return;
+ }
+
+ $lines = file(static::PATH_ENV, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ if ($lines === false) {
+ throw new Exception('Unable to read environment file "' . static::PATH_ENV . '"');
+ }
+
+ foreach ($lines as $line) {
+ if (preg_match('/^\s*(?:(?#).+|(?[a-zA-Z0-9_-]+)=(?.+))$/i', $line, $match) !== 1) {
+ continue; // Ligne invalide
+ }
+
+ if (isset($match['comment']) && trim($match['comment']) !== '') {
+ continue; // Commentaire
+ }
+
+ $this->properties[mb_strtoupper($match['key'])] = $match['value'];
+ }
+ }
+}
\ No newline at end of file