* Overwrite {@see BaseEnv::initProperties()} to set initial properties */ abstract class BaseEnv { 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']; } } }