work:intermediate
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
d434567899
commit
23c912b0c0
@ -1,34 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
function loadEnv($filePath) {
|
|
||||||
if (!file_exists($filePath)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
use Dotenv\Dotenv;
|
||||||
if ($lines === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($lines as $line) {
|
$dotenv = Dotenv::createImmutable(__DIR__);
|
||||||
// Skip comments
|
$dotenv->load();
|
||||||
if (strpos(trim($line), '#') === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
list($key, $value) = explode('=', $line, 2);
|
// const DB_HOST = $_ENV['DB_HOST'] ?? 'localhost';
|
||||||
$_ENV[trim($key)] = trim($value);
|
// const DB_DATABASE = $_ENV['DB_DATABASE'] ?? 'heartTrack';
|
||||||
}
|
// const DB_USER = $_ENV['DB_USER'] ?? 'toto';
|
||||||
|
// const DB_PASSWORD = $_ENV['DB_PASSWORD'] ?? 'achanger';
|
||||||
|
// const APP_ENV = $_ENV['APP_ENV'] ?? 'development';
|
||||||
|
const DB_HOST ='localhost';
|
||||||
|
const DB_DATABASE = 'heartTrack';
|
||||||
|
const DB_USER = 'toto';
|
||||||
|
const DB_PASSWORD = 'achanger';
|
||||||
|
const APP_ENV = 'console';
|
||||||
|
|
||||||
return true;
|
const DSN = "mysql:host=" . DB_HOST . ";dbname=" . DB_DATABASE;
|
||||||
}
|
|
||||||
|
|
||||||
// Load the .env file
|
|
||||||
loadEnv(__DIR__ . '/.env');
|
|
||||||
|
|
||||||
const DB_HOST = isset($_ENV['DB_HOST']) ? $_ENV['DB_HOST'] : 'localhost';
|
|
||||||
const DB_DATABASE = isset($_ENV['DB_DATABASE']) ? $_ENV['DB_DATABASE'] : 'heartTrack';
|
|
||||||
const DB_USER = isset($_ENV['DB_USER']) ? $_ENV['DB_USER'] : 'toto';
|
|
||||||
const DB_PASSWORD = isset($_ENV['DB_PASSWORD']) ? $_ENV['DB_PASSWORD'] : 'achanger';
|
|
||||||
const APP_ENV = isset($_ENV['APP_ENV']) ? $_ENV['APP_ENV'] : 'development';
|
|
||||||
|
|
||||||
const dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_DATABASE;
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
require '../vendor/autoload.php';
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
if ('development') {
|
require_once __DIR__ . '/../config/config.php';
|
||||||
require('index.html');
|
if (APP_ENV === 'console') {
|
||||||
|
require_once __DIR__ . '/../src/console/Console.php';
|
||||||
|
}
|
||||||
|
elseif (APP_ENV === 'development') {
|
||||||
|
require_once __DIR__ . 'index.html';
|
||||||
}
|
}
|
@ -1,15 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Model;
|
namespace Repository;
|
||||||
|
|
||||||
interface IGenericRepository
|
interface IGenericRepository
|
||||||
{
|
{
|
||||||
public function getItemById();
|
public function getItemById(int $id);
|
||||||
public function GetNbItems(): int;
|
public function GetNbItems(): int;
|
||||||
public function GetItems(int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array;
|
public function GetItems(int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array;
|
||||||
public function GetItemsByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array;
|
public function GetItemsByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array;
|
||||||
public function GetItemByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false): array;
|
public function GetItemByName(string $substring, int $index, int $count, ?string $orderingPropertyName = null, bool $descending = false);
|
||||||
public function UpdateItem($oldItem, $newItem);
|
public function UpdateItem($oldItem, $newItem) : void;
|
||||||
public function AddItem($item);
|
public function AddItem($item) : void;
|
||||||
public function DeleteItem($item): bool;
|
public function DeleteItem($item): bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Stub;
|
namespace Stub;
|
||||||
|
use Shared\HashPassword;
|
||||||
|
use Stub\AuthService;
|
||||||
use Manager\DataManager;
|
use Manager\DataManager;
|
||||||
use Manager\UserManager;
|
use Manager\UserManager;
|
||||||
|
use Stub\UserRepository;
|
||||||
|
|
||||||
class StubData extends DataManager{
|
class StubData extends DataManager{
|
||||||
public function __construct(){
|
public function __construct(){
|
||||||
$userMgr = new UserManager(new AuthService(new UserRepository()));
|
$this->userMgr = new UserManager(new AuthService(new UserRepository(),new HashPassword()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shared;
|
||||||
|
|
||||||
|
class HashPassword implements IHashPassword{
|
||||||
|
public const MAX_PASSWORD_LENGTH = 4096;
|
||||||
|
public function hashPassword(string $rawPassword): string
|
||||||
|
{
|
||||||
|
if (empty($rawPassword)) {
|
||||||
|
throw new \InvalidArgumentException("Password to hash is invalid");
|
||||||
|
}
|
||||||
|
if (strlen($rawPassword) > self::MAX_PASSWORD_LENGTH)
|
||||||
|
{
|
||||||
|
throw new \InvalidArgumentException("Password too long");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a password hash using a secure algorithm
|
||||||
|
$hash = password_hash($rawPassword, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
if ($hash === false) {
|
||||||
|
throw new \RuntimeException("Password hashing failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPasswordValid(string $hashedPassword, string $rawPassword): bool
|
||||||
|
{
|
||||||
|
if (empty($hashedPassword) || empty($rawPassword)) {
|
||||||
|
throw new \InvalidArgumentException("Encoded password or raw password is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use password_verify to check if the raw password matches the encoded one
|
||||||
|
return password_verify($rawPassword, $hashedPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shared;
|
||||||
|
|
||||||
|
interface IHashPassword
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Hash the raw password.
|
||||||
|
*
|
||||||
|
* @return string The hashed password
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the password to hash is invalid
|
||||||
|
*/
|
||||||
|
public function hashPassword(string $raw) : string ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks a raw password against an hash password.
|
||||||
|
*
|
||||||
|
* @param string $hashed An hash password
|
||||||
|
* @param string $raw A raw password
|
||||||
|
*
|
||||||
|
* @return bool true if the password is valid, false otherwise
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException If the salt is invalid
|
||||||
|
*/
|
||||||
|
public function isPasswordValid(string $hashed, string $raw) : bool;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Utils;
|
namespace Shared;
|
||||||
use \Exception;
|
use \Exception;
|
||||||
final class Validation {
|
final class Validation {
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Exception;
|
namespace Shared\Exception;
|
||||||
|
|
||||||
final class NotImplementedException extends \Exception
|
final class NotImplementedException extends \Exception
|
||||||
{
|
{
|
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2020-2023 Graham Campbell <hello@gjcampbell.co.uk>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "graham-campbell/result-type",
|
||||||
|
"description": "An Implementation Of The Result Type",
|
||||||
|
"keywords": ["result", "result-type", "Result", "Result Type", "Result-Type", "Graham Campbell", "GrahamCampbell"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "hello@gjcampbell.co.uk",
|
||||||
|
"homepage": "https://github.com/GrahamCampbell"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5 || ^8.0",
|
||||||
|
"phpoption/phpoption": "^1.9.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"GrahamCampbell\\ResultType\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"GrahamCampbell\\Tests\\ResultType\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"preferred-install": "dist"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Result Type.
|
||||||
|
*
|
||||||
|
* (c) Graham Campbell <hello@gjcampbell.co.uk>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace GrahamCampbell\ResultType;
|
||||||
|
|
||||||
|
use PhpOption\None;
|
||||||
|
use PhpOption\Some;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @template E
|
||||||
|
* @extends \GrahamCampbell\ResultType\Result<T,E>
|
||||||
|
*/
|
||||||
|
final class Error extends Result
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var E
|
||||||
|
*/
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor for an error value.
|
||||||
|
*
|
||||||
|
* @param E $value
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct($value)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new error value.
|
||||||
|
*
|
||||||
|
* @template F
|
||||||
|
*
|
||||||
|
* @param F $value
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<T,F>
|
||||||
|
*/
|
||||||
|
public static function create($value)
|
||||||
|
{
|
||||||
|
return new self($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the success option value.
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<T>
|
||||||
|
*/
|
||||||
|
public function success()
|
||||||
|
{
|
||||||
|
return None::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map over the success value.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param callable(T):S $f
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<S,E>
|
||||||
|
*/
|
||||||
|
public function map(callable $f)
|
||||||
|
{
|
||||||
|
return self::create($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flat map over the success value.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
* @template F
|
||||||
|
*
|
||||||
|
* @param callable(T):\GrahamCampbell\ResultType\Result<S,F> $f
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<S,F>
|
||||||
|
*/
|
||||||
|
public function flatMap(callable $f)
|
||||||
|
{
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<S,F> */
|
||||||
|
return self::create($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the error option value.
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<E>
|
||||||
|
*/
|
||||||
|
public function error()
|
||||||
|
{
|
||||||
|
return Some::create($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map over the error value.
|
||||||
|
*
|
||||||
|
* @template F
|
||||||
|
*
|
||||||
|
* @param callable(E):F $f
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<T,F>
|
||||||
|
*/
|
||||||
|
public function mapError(callable $f)
|
||||||
|
{
|
||||||
|
return self::create($f($this->value));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Result Type.
|
||||||
|
*
|
||||||
|
* (c) Graham Campbell <hello@gjcampbell.co.uk>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace GrahamCampbell\ResultType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @template E
|
||||||
|
*/
|
||||||
|
abstract class Result
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the success option value.
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<T>
|
||||||
|
*/
|
||||||
|
abstract public function success();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map over the success value.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param callable(T):S $f
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<S,E>
|
||||||
|
*/
|
||||||
|
abstract public function map(callable $f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flat map over the success value.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
* @template F
|
||||||
|
*
|
||||||
|
* @param callable(T):\GrahamCampbell\ResultType\Result<S,F> $f
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<S,F>
|
||||||
|
*/
|
||||||
|
abstract public function flatMap(callable $f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the error option value.
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<E>
|
||||||
|
*/
|
||||||
|
abstract public function error();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map over the error value.
|
||||||
|
*
|
||||||
|
* @template F
|
||||||
|
*
|
||||||
|
* @param callable(E):F $f
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<T,F>
|
||||||
|
*/
|
||||||
|
abstract public function mapError(callable $f);
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Result Type.
|
||||||
|
*
|
||||||
|
* (c) Graham Campbell <hello@gjcampbell.co.uk>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace GrahamCampbell\ResultType;
|
||||||
|
|
||||||
|
use PhpOption\None;
|
||||||
|
use PhpOption\Some;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @template E
|
||||||
|
* @extends \GrahamCampbell\ResultType\Result<T,E>
|
||||||
|
*/
|
||||||
|
final class Success extends Result
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var T
|
||||||
|
*/
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor for a success value.
|
||||||
|
*
|
||||||
|
* @param T $value
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct($value)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new error value.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param S $value
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<S,E>
|
||||||
|
*/
|
||||||
|
public static function create($value)
|
||||||
|
{
|
||||||
|
return new self($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the success option value.
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<T>
|
||||||
|
*/
|
||||||
|
public function success()
|
||||||
|
{
|
||||||
|
return Some::create($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map over the success value.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param callable(T):S $f
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<S,E>
|
||||||
|
*/
|
||||||
|
public function map(callable $f)
|
||||||
|
{
|
||||||
|
return self::create($f($this->value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flat map over the success value.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
* @template F
|
||||||
|
*
|
||||||
|
* @param callable(T):\GrahamCampbell\ResultType\Result<S,F> $f
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<S,F>
|
||||||
|
*/
|
||||||
|
public function flatMap(callable $f)
|
||||||
|
{
|
||||||
|
return $f($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the error option value.
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<E>
|
||||||
|
*/
|
||||||
|
public function error()
|
||||||
|
{
|
||||||
|
return None::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map over the error value.
|
||||||
|
*
|
||||||
|
* @template F
|
||||||
|
*
|
||||||
|
* @param callable(E):F $f
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<T,F>
|
||||||
|
*/
|
||||||
|
public function mapError(callable $f)
|
||||||
|
{
|
||||||
|
return self::create($this->value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "phpoption/phpoption",
|
||||||
|
"description": "Option Type for PHP",
|
||||||
|
"keywords": ["php", "option", "language", "type"],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Johannes M. Schmitt",
|
||||||
|
"email": "schmittjoh@gmail.com",
|
||||||
|
"homepage": "https://github.com/schmittjoh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "hello@gjcampbell.co.uk",
|
||||||
|
"homepage": "https://github.com/GrahamCampbell"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
|
"phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PhpOption\\": "src/PhpOption/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"PhpOption\\Tests\\": "tests/PhpOption/Tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"bamarni/composer-bin-plugin": true
|
||||||
|
},
|
||||||
|
"preferred-install": "dist"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"bamarni-bin": {
|
||||||
|
"bin-links": true,
|
||||||
|
"forward-command": true
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.9-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true
|
||||||
|
}
|
@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace PhpOption;
|
||||||
|
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
*
|
||||||
|
* @extends Option<T>
|
||||||
|
*/
|
||||||
|
final class LazyOption extends Option
|
||||||
|
{
|
||||||
|
/** @var callable(mixed...):(Option<T>) */
|
||||||
|
private $callback;
|
||||||
|
|
||||||
|
/** @var array<int, mixed> */
|
||||||
|
private $arguments;
|
||||||
|
|
||||||
|
/** @var Option<T>|null */
|
||||||
|
private $option;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template S
|
||||||
|
* @param callable(mixed...):(Option<S>) $callback
|
||||||
|
* @param array<int, mixed> $arguments
|
||||||
|
*
|
||||||
|
* @return LazyOption<S>
|
||||||
|
*/
|
||||||
|
public static function create($callback, array $arguments = []): self
|
||||||
|
{
|
||||||
|
return new self($callback, $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable(mixed...):(Option<T>) $callback
|
||||||
|
* @param array<int, mixed> $arguments
|
||||||
|
*/
|
||||||
|
public function __construct($callback, array $arguments = [])
|
||||||
|
{
|
||||||
|
if (!is_callable($callback)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid callback given');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->callback = $callback;
|
||||||
|
$this->arguments = $arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isDefined(): bool
|
||||||
|
{
|
||||||
|
return $this->option()->isDefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEmpty(): bool
|
||||||
|
{
|
||||||
|
return $this->option()->isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get()
|
||||||
|
{
|
||||||
|
return $this->option()->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrElse($default)
|
||||||
|
{
|
||||||
|
return $this->option()->getOrElse($default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrCall($callable)
|
||||||
|
{
|
||||||
|
return $this->option()->getOrCall($callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrThrow(\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->option()->getOrThrow($ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orElse(Option $else)
|
||||||
|
{
|
||||||
|
return $this->option()->orElse($else);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ifDefined($callable)
|
||||||
|
{
|
||||||
|
$this->option()->forAll($callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forAll($callable)
|
||||||
|
{
|
||||||
|
return $this->option()->forAll($callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function map($callable)
|
||||||
|
{
|
||||||
|
return $this->option()->map($callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flatMap($callable)
|
||||||
|
{
|
||||||
|
return $this->option()->flatMap($callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filter($callable)
|
||||||
|
{
|
||||||
|
return $this->option()->filter($callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filterNot($callable)
|
||||||
|
{
|
||||||
|
return $this->option()->filterNot($callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function select($value)
|
||||||
|
{
|
||||||
|
return $this->option()->select($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reject($value)
|
||||||
|
{
|
||||||
|
return $this->option()->reject($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Traversable<T>
|
||||||
|
*/
|
||||||
|
public function getIterator(): Traversable
|
||||||
|
{
|
||||||
|
return $this->option()->getIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function foldLeft($initialValue, $callable)
|
||||||
|
{
|
||||||
|
return $this->option()->foldLeft($initialValue, $callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function foldRight($initialValue, $callable)
|
||||||
|
{
|
||||||
|
return $this->option()->foldRight($initialValue, $callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Option<T>
|
||||||
|
*/
|
||||||
|
private function option(): Option
|
||||||
|
{
|
||||||
|
if (null === $this->option) {
|
||||||
|
/** @var mixed */
|
||||||
|
$option = call_user_func_array($this->callback, $this->arguments);
|
||||||
|
if ($option instanceof Option) {
|
||||||
|
$this->option = $option;
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException(sprintf('Expected instance of %s', Option::class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->option;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace PhpOption;
|
||||||
|
|
||||||
|
use EmptyIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends Option<mixed>
|
||||||
|
*/
|
||||||
|
final class None extends Option
|
||||||
|
{
|
||||||
|
/** @var None|null */
|
||||||
|
private static $instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return None
|
||||||
|
*/
|
||||||
|
public static function create(): self
|
||||||
|
{
|
||||||
|
if (null === self::$instance) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get()
|
||||||
|
{
|
||||||
|
throw new \RuntimeException('None has no value.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrCall($callable)
|
||||||
|
{
|
||||||
|
return $callable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrElse($default)
|
||||||
|
{
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrThrow(\Exception $ex)
|
||||||
|
{
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEmpty(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isDefined(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orElse(Option $else)
|
||||||
|
{
|
||||||
|
return $else;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ifDefined($callable)
|
||||||
|
{
|
||||||
|
// Just do nothing in that case.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forAll($callable)
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function map($callable)
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flatMap($callable)
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filter($callable)
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filterNot($callable)
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function select($value)
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reject($value)
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIterator(): EmptyIterator
|
||||||
|
{
|
||||||
|
return new EmptyIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function foldLeft($initialValue, $callable)
|
||||||
|
{
|
||||||
|
return $initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function foldRight($initialValue, $callable)
|
||||||
|
{
|
||||||
|
return $initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,434 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace PhpOption;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use IteratorAggregate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
*
|
||||||
|
* @implements IteratorAggregate<T>
|
||||||
|
*/
|
||||||
|
abstract class Option implements IteratorAggregate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates an option given a return value.
|
||||||
|
*
|
||||||
|
* This is intended for consuming existing APIs and allows you to easily
|
||||||
|
* convert them to an option. By default, we treat ``null`` as the None
|
||||||
|
* case, and everything else as Some.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param S $value The actual return value.
|
||||||
|
* @param S $noneValue The value which should be considered "None"; null by
|
||||||
|
* default.
|
||||||
|
*
|
||||||
|
* @return Option<S>
|
||||||
|
*/
|
||||||
|
public static function fromValue($value, $noneValue = null)
|
||||||
|
{
|
||||||
|
if ($value === $noneValue) {
|
||||||
|
return None::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Some($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an option from an array's value.
|
||||||
|
*
|
||||||
|
* If the key does not exist in the array, the array is not actually an
|
||||||
|
* array, or the array's value at the given key is null, None is returned.
|
||||||
|
* Otherwise, Some is returned wrapping the value at the given key.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param array<string|int,S>|ArrayAccess<string|int,S>|null $array A potential array or \ArrayAccess value.
|
||||||
|
* @param string $key The key to check.
|
||||||
|
*
|
||||||
|
* @return Option<S>
|
||||||
|
*/
|
||||||
|
public static function fromArraysValue($array, $key)
|
||||||
|
{
|
||||||
|
if (!(is_array($array) || $array instanceof ArrayAccess) || !isset($array[$key])) {
|
||||||
|
return None::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Some($array[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a lazy-option with the given callback.
|
||||||
|
*
|
||||||
|
* This is also a helper constructor for lazy-consuming existing APIs where
|
||||||
|
* the return value is not yet an option. By default, we treat ``null`` as
|
||||||
|
* None case, and everything else as Some.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param callable $callback The callback to evaluate.
|
||||||
|
* @param array $arguments The arguments for the callback.
|
||||||
|
* @param S $noneValue The value which should be considered "None";
|
||||||
|
* null by default.
|
||||||
|
*
|
||||||
|
* @return LazyOption<S>
|
||||||
|
*/
|
||||||
|
public static function fromReturn($callback, array $arguments = [], $noneValue = null)
|
||||||
|
{
|
||||||
|
return new LazyOption(static function () use ($callback, $arguments, $noneValue) {
|
||||||
|
/** @var mixed */
|
||||||
|
$return = call_user_func_array($callback, $arguments);
|
||||||
|
|
||||||
|
if ($return === $noneValue) {
|
||||||
|
return None::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Some($return);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option factory, which creates new option based on passed value.
|
||||||
|
*
|
||||||
|
* If value is already an option, it simply returns. If value is callable,
|
||||||
|
* LazyOption with passed callback created and returned. If Option
|
||||||
|
* returned from callback, it returns directly. On other case value passed
|
||||||
|
* to Option::fromValue() method.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param Option<S>|callable|S $value
|
||||||
|
* @param S $noneValue Used when $value is mixed or
|
||||||
|
* callable, for None-check.
|
||||||
|
*
|
||||||
|
* @return Option<S>|LazyOption<S>
|
||||||
|
*/
|
||||||
|
public static function ensure($value, $noneValue = null)
|
||||||
|
{
|
||||||
|
if ($value instanceof self) {
|
||||||
|
return $value;
|
||||||
|
} elseif (is_callable($value)) {
|
||||||
|
return new LazyOption(static function () use ($value, $noneValue) {
|
||||||
|
/** @var mixed */
|
||||||
|
$return = $value();
|
||||||
|
|
||||||
|
if ($return instanceof self) {
|
||||||
|
return $return;
|
||||||
|
} else {
|
||||||
|
return self::fromValue($return, $noneValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return self::fromValue($value, $noneValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lift a function so that it accepts Option as parameters.
|
||||||
|
*
|
||||||
|
* We return a new closure that wraps the original callback. If any of the
|
||||||
|
* parameters passed to the lifted function is empty, the function will
|
||||||
|
* return a value of None. Otherwise, we will pass all parameters to the
|
||||||
|
* original callback and return the value inside a new Option, unless an
|
||||||
|
* Option is returned from the function, in which case, we use that.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param callable $callback
|
||||||
|
* @param mixed $noneValue
|
||||||
|
*
|
||||||
|
* @return callable
|
||||||
|
*/
|
||||||
|
public static function lift($callback, $noneValue = null)
|
||||||
|
{
|
||||||
|
return static function () use ($callback, $noneValue) {
|
||||||
|
/** @var array<int, mixed> */
|
||||||
|
$args = func_get_args();
|
||||||
|
|
||||||
|
$reduced_args = array_reduce(
|
||||||
|
$args,
|
||||||
|
/** @param bool $status */
|
||||||
|
static function ($status, self $o) {
|
||||||
|
return $o->isEmpty() ? true : $status;
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
// if at least one parameter is empty, return None
|
||||||
|
if ($reduced_args) {
|
||||||
|
return None::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_map(
|
||||||
|
/** @return T */
|
||||||
|
static function (self $o) {
|
||||||
|
// it is safe to do so because the fold above checked
|
||||||
|
// that all arguments are of type Some
|
||||||
|
/** @var T */
|
||||||
|
return $o->get();
|
||||||
|
},
|
||||||
|
$args
|
||||||
|
);
|
||||||
|
|
||||||
|
return self::ensure(call_user_func_array($callback, $args), $noneValue);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value if available, or throws an exception otherwise.
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException If value is not available.
|
||||||
|
*
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
abstract public function get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value if available, or the default value if not.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param S $default
|
||||||
|
*
|
||||||
|
* @return T|S
|
||||||
|
*/
|
||||||
|
abstract public function getOrElse($default);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value if available, or the results of the callable.
|
||||||
|
*
|
||||||
|
* This is preferable over ``getOrElse`` if the computation of the default
|
||||||
|
* value is expensive.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param callable():S $callable
|
||||||
|
*
|
||||||
|
* @return T|S
|
||||||
|
*/
|
||||||
|
abstract public function getOrCall($callable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value if available, or throws the passed exception.
|
||||||
|
*
|
||||||
|
* @param \Exception $ex
|
||||||
|
*
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
abstract public function getOrThrow(\Exception $ex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if no value is available, false otherwise.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
abstract public function isEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a value is available, false otherwise.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
abstract public function isDefined();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this option if non-empty, or the passed option otherwise.
|
||||||
|
*
|
||||||
|
* This can be used to try multiple alternatives, and is especially useful
|
||||||
|
* with lazy evaluating options:
|
||||||
|
*
|
||||||
|
* ```php
|
||||||
|
* $repo->findSomething()
|
||||||
|
* ->orElse(new LazyOption(array($repo, 'findSomethingElse')))
|
||||||
|
* ->orElse(new LazyOption(array($repo, 'createSomething')));
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param Option<T> $else
|
||||||
|
*
|
||||||
|
* @return Option<T>
|
||||||
|
*/
|
||||||
|
abstract public function orElse(self $else);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is similar to map() below except that the return value has no meaning;
|
||||||
|
* the passed callable is simply executed if the option is non-empty, and
|
||||||
|
* ignored if the option is empty.
|
||||||
|
*
|
||||||
|
* In all cases, the return value of the callable is discarded.
|
||||||
|
*
|
||||||
|
* ```php
|
||||||
|
* $comment->getMaybeFile()->ifDefined(function($file) {
|
||||||
|
* // Do something with $file here.
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* If you're looking for something like ``ifEmpty``, you can use ``getOrCall``
|
||||||
|
* and ``getOrElse`` in these cases.
|
||||||
|
*
|
||||||
|
* @deprecated Use forAll() instead.
|
||||||
|
*
|
||||||
|
* @param callable(T):mixed $callable
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
abstract public function ifDefined($callable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is similar to map() except that the return value of the callable has no meaning.
|
||||||
|
*
|
||||||
|
* The passed callable is simply executed if the option is non-empty, and ignored if the
|
||||||
|
* option is empty. This method is preferred for callables with side-effects, while map()
|
||||||
|
* is intended for callables without side-effects.
|
||||||
|
*
|
||||||
|
* @param callable(T):mixed $callable
|
||||||
|
*
|
||||||
|
* @return Option<T>
|
||||||
|
*/
|
||||||
|
abstract public function forAll($callable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the callable to the value of the option if it is non-empty,
|
||||||
|
* and returns the return value of the callable wrapped in Some().
|
||||||
|
*
|
||||||
|
* If the option is empty, then the callable is not applied.
|
||||||
|
*
|
||||||
|
* ```php
|
||||||
|
* (new Some("foo"))->map('strtoupper')->get(); // "FOO"
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param callable(T):S $callable
|
||||||
|
*
|
||||||
|
* @return Option<S>
|
||||||
|
*/
|
||||||
|
abstract public function map($callable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the callable to the value of the option if it is non-empty, and
|
||||||
|
* returns the return value of the callable directly.
|
||||||
|
*
|
||||||
|
* In contrast to ``map``, the return value of the callable is expected to
|
||||||
|
* be an Option itself; it is not automatically wrapped in Some().
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param callable(T):Option<S> $callable must return an Option
|
||||||
|
*
|
||||||
|
* @return Option<S>
|
||||||
|
*/
|
||||||
|
abstract public function flatMap($callable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the option is empty, it is returned immediately without applying the callable.
|
||||||
|
*
|
||||||
|
* If the option is non-empty, the callable is applied, and if it returns true,
|
||||||
|
* the option itself is returned; otherwise, None is returned.
|
||||||
|
*
|
||||||
|
* @param callable(T):bool $callable
|
||||||
|
*
|
||||||
|
* @return Option<T>
|
||||||
|
*/
|
||||||
|
abstract public function filter($callable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the option is empty, it is returned immediately without applying the callable.
|
||||||
|
*
|
||||||
|
* If the option is non-empty, the callable is applied, and if it returns false,
|
||||||
|
* the option itself is returned; otherwise, None is returned.
|
||||||
|
*
|
||||||
|
* @param callable(T):bool $callable
|
||||||
|
*
|
||||||
|
* @return Option<T>
|
||||||
|
*/
|
||||||
|
abstract public function filterNot($callable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the option is empty, it is returned immediately.
|
||||||
|
*
|
||||||
|
* If the option is non-empty, and its value does not equal the passed value
|
||||||
|
* (via a shallow comparison ===), then None is returned. Otherwise, the
|
||||||
|
* Option is returned.
|
||||||
|
*
|
||||||
|
* In other words, this will filter all but the passed value.
|
||||||
|
*
|
||||||
|
* @param T $value
|
||||||
|
*
|
||||||
|
* @return Option<T>
|
||||||
|
*/
|
||||||
|
abstract public function select($value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the option is empty, it is returned immediately.
|
||||||
|
*
|
||||||
|
* If the option is non-empty, and its value does equal the passed value (via
|
||||||
|
* a shallow comparison ===), then None is returned; otherwise, the Option is
|
||||||
|
* returned.
|
||||||
|
*
|
||||||
|
* In other words, this will let all values through except the passed value.
|
||||||
|
*
|
||||||
|
* @param T $value
|
||||||
|
*
|
||||||
|
* @return Option<T>
|
||||||
|
*/
|
||||||
|
abstract public function reject($value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binary operator for the initial value and the option's value.
|
||||||
|
*
|
||||||
|
* If empty, the initial value is returned. If non-empty, the callable
|
||||||
|
* receives the initial value and the option's value as arguments.
|
||||||
|
*
|
||||||
|
* ```php
|
||||||
|
*
|
||||||
|
* $some = new Some(5);
|
||||||
|
* $none = None::create();
|
||||||
|
* $result = $some->foldLeft(1, function($a, $b) { return $a + $b; }); // int(6)
|
||||||
|
* $result = $none->foldLeft(1, function($a, $b) { return $a + $b; }); // int(1)
|
||||||
|
*
|
||||||
|
* // This can be used instead of something like the following:
|
||||||
|
* $option = Option::fromValue($integerOrNull);
|
||||||
|
* $result = 1;
|
||||||
|
* if ( ! $option->isEmpty()) {
|
||||||
|
* $result += $option->get();
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param S $initialValue
|
||||||
|
* @param callable(S, T):S $callable
|
||||||
|
*
|
||||||
|
* @return S
|
||||||
|
*/
|
||||||
|
abstract public function foldLeft($initialValue, $callable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* foldLeft() but with reversed arguments for the callable.
|
||||||
|
*
|
||||||
|
* @template S
|
||||||
|
*
|
||||||
|
* @param S $initialValue
|
||||||
|
* @param callable(T, S):S $callable
|
||||||
|
*
|
||||||
|
* @return S
|
||||||
|
*/
|
||||||
|
abstract public function foldRight($initialValue, $callable);
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace PhpOption;
|
||||||
|
|
||||||
|
use ArrayIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
*
|
||||||
|
* @extends Option<T>
|
||||||
|
*/
|
||||||
|
final class Some extends Option
|
||||||
|
{
|
||||||
|
/** @var T */
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param T $value
|
||||||
|
*/
|
||||||
|
public function __construct($value)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template U
|
||||||
|
*
|
||||||
|
* @param U $value
|
||||||
|
*
|
||||||
|
* @return Some<U>
|
||||||
|
*/
|
||||||
|
public static function create($value): self
|
||||||
|
{
|
||||||
|
return new self($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isDefined(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEmpty(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get()
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrElse($default)
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrCall($callable)
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOrThrow(\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orElse(Option $else)
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ifDefined($callable)
|
||||||
|
{
|
||||||
|
$this->forAll($callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forAll($callable)
|
||||||
|
{
|
||||||
|
$callable($this->value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function map($callable)
|
||||||
|
{
|
||||||
|
return new self($callable($this->value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flatMap($callable)
|
||||||
|
{
|
||||||
|
/** @var mixed */
|
||||||
|
$rs = $callable($this->value);
|
||||||
|
if (!$rs instanceof Option) {
|
||||||
|
throw new \RuntimeException('Callables passed to flatMap() must return an Option. Maybe you should use map() instead?');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filter($callable)
|
||||||
|
{
|
||||||
|
if (true === $callable($this->value)) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return None::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filterNot($callable)
|
||||||
|
{
|
||||||
|
if (false === $callable($this->value)) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return None::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function select($value)
|
||||||
|
{
|
||||||
|
if ($this->value === $value) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return None::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reject($value)
|
||||||
|
{
|
||||||
|
if ($this->value === $value) {
|
||||||
|
return None::create();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ArrayIterator<int, T>
|
||||||
|
*/
|
||||||
|
public function getIterator(): ArrayIterator
|
||||||
|
{
|
||||||
|
return new ArrayIterator([$this->value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function foldLeft($initialValue, $callable)
|
||||||
|
{
|
||||||
|
return $callable($initialValue, $this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function foldRight($initialValue, $callable)
|
||||||
|
{
|
||||||
|
return $callable($this->value, $initialValue);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2020-present Fabien Potencier
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Polyfill\Php80;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Ion Bazan <ion.bazan@gmail.com>
|
||||||
|
* @author Nico Oelgart <nicoswd@gmail.com>
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Php80
|
||||||
|
{
|
||||||
|
public static function fdiv(float $dividend, float $divisor): float
|
||||||
|
{
|
||||||
|
return @($dividend / $divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get_debug_type($value): string
|
||||||
|
{
|
||||||
|
switch (true) {
|
||||||
|
case null === $value: return 'null';
|
||||||
|
case \is_bool($value): return 'bool';
|
||||||
|
case \is_string($value): return 'string';
|
||||||
|
case \is_array($value): return 'array';
|
||||||
|
case \is_int($value): return 'int';
|
||||||
|
case \is_float($value): return 'float';
|
||||||
|
case \is_object($value): break;
|
||||||
|
case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class';
|
||||||
|
default:
|
||||||
|
if (null === $type = @get_resource_type($value)) {
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('Unknown' === $type) {
|
||||||
|
$type = 'closed';
|
||||||
|
}
|
||||||
|
|
||||||
|
return "resource ($type)";
|
||||||
|
}
|
||||||
|
|
||||||
|
$class = \get_class($value);
|
||||||
|
|
||||||
|
if (false === strpos($class, '@')) {
|
||||||
|
return $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get_resource_id($res): int
|
||||||
|
{
|
||||||
|
if (!\is_resource($res) && null === @get_resource_type($res)) {
|
||||||
|
throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function preg_last_error_msg(): string
|
||||||
|
{
|
||||||
|
switch (preg_last_error()) {
|
||||||
|
case \PREG_INTERNAL_ERROR:
|
||||||
|
return 'Internal error';
|
||||||
|
case \PREG_BAD_UTF8_ERROR:
|
||||||
|
return 'Malformed UTF-8 characters, possibly incorrectly encoded';
|
||||||
|
case \PREG_BAD_UTF8_OFFSET_ERROR:
|
||||||
|
return 'The offset did not correspond to the beginning of a valid UTF-8 code point';
|
||||||
|
case \PREG_BACKTRACK_LIMIT_ERROR:
|
||||||
|
return 'Backtrack limit exhausted';
|
||||||
|
case \PREG_RECURSION_LIMIT_ERROR:
|
||||||
|
return 'Recursion limit exhausted';
|
||||||
|
case \PREG_JIT_STACKLIMIT_ERROR:
|
||||||
|
return 'JIT stack limit exhausted';
|
||||||
|
case \PREG_NO_ERROR:
|
||||||
|
return 'No error';
|
||||||
|
default:
|
||||||
|
return 'Unknown error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function str_contains(string $haystack, string $needle): bool
|
||||||
|
{
|
||||||
|
return '' === $needle || false !== strpos($haystack, $needle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function str_starts_with(string $haystack, string $needle): bool
|
||||||
|
{
|
||||||
|
return 0 === strncmp($haystack, $needle, \strlen($needle));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function str_ends_with(string $haystack, string $needle): bool
|
||||||
|
{
|
||||||
|
if ('' === $needle || $needle === $haystack) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' === $haystack) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$needleLength = \strlen($needle);
|
||||||
|
|
||||||
|
return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Polyfill\Php80;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fedonyuk Anton <info@ensostudio.ru>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class PhpToken implements \Stringable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $text;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $line;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $pos;
|
||||||
|
|
||||||
|
public function __construct(int $id, string $text, int $line = -1, int $position = -1)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
$this->text = $text;
|
||||||
|
$this->line = $line;
|
||||||
|
$this->pos = $position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTokenName(): ?string
|
||||||
|
{
|
||||||
|
if ('UNKNOWN' === $name = token_name($this->id)) {
|
||||||
|
$name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int|string|array $kind
|
||||||
|
*/
|
||||||
|
public function is($kind): bool
|
||||||
|
{
|
||||||
|
foreach ((array) $kind as $value) {
|
||||||
|
if (\in_array($value, [$this->id, $this->text], true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isIgnorable(): bool
|
||||||
|
{
|
||||||
|
return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return (string) $this->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return static[]
|
||||||
|
*/
|
||||||
|
public static function tokenize(string $code, int $flags = 0): array
|
||||||
|
{
|
||||||
|
$line = 1;
|
||||||
|
$position = 0;
|
||||||
|
$tokens = token_get_all($code, $flags);
|
||||||
|
foreach ($tokens as $index => $token) {
|
||||||
|
if (\is_string($token)) {
|
||||||
|
$id = \ord($token);
|
||||||
|
$text = $token;
|
||||||
|
} else {
|
||||||
|
[$id, $text, $line] = $token;
|
||||||
|
}
|
||||||
|
$tokens[$index] = new static($id, $text, $line, $position);
|
||||||
|
$position += \strlen($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
Symfony Polyfill / Php80
|
||||||
|
========================
|
||||||
|
|
||||||
|
This component provides features added to PHP 8.0 core:
|
||||||
|
|
||||||
|
- [`Stringable`](https://php.net/stringable) interface
|
||||||
|
- [`fdiv`](https://php.net/fdiv)
|
||||||
|
- [`ValueError`](https://php.net/valueerror) class
|
||||||
|
- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class
|
||||||
|
- `FILTER_VALIDATE_BOOL` constant
|
||||||
|
- [`get_debug_type`](https://php.net/get_debug_type)
|
||||||
|
- [`PhpToken`](https://php.net/phptoken) class
|
||||||
|
- [`preg_last_error_msg`](https://php.net/preg_last_error_msg)
|
||||||
|
- [`str_contains`](https://php.net/str_contains)
|
||||||
|
- [`str_starts_with`](https://php.net/str_starts_with)
|
||||||
|
- [`str_ends_with`](https://php.net/str_ends_with)
|
||||||
|
- [`get_resource_id`](https://php.net/get_resource_id)
|
||||||
|
|
||||||
|
More information can be found in the
|
||||||
|
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
This library is released under the [MIT license](LICENSE).
|
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_CLASS)]
|
||||||
|
final class Attribute
|
||||||
|
{
|
||||||
|
public const TARGET_CLASS = 1;
|
||||||
|
public const TARGET_FUNCTION = 2;
|
||||||
|
public const TARGET_METHOD = 4;
|
||||||
|
public const TARGET_PROPERTY = 8;
|
||||||
|
public const TARGET_CLASS_CONSTANT = 16;
|
||||||
|
public const TARGET_PARAMETER = 32;
|
||||||
|
public const TARGET_ALL = 63;
|
||||||
|
public const IS_REPEATABLE = 64;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
public $flags;
|
||||||
|
|
||||||
|
public function __construct(int $flags = self::TARGET_ALL)
|
||||||
|
{
|
||||||
|
$this->flags = $flags;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) {
|
||||||
|
class PhpToken extends Symfony\Polyfill\Php80\PhpToken
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID < 80000) {
|
||||||
|
interface Stringable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID < 80000) {
|
||||||
|
class UnhandledMatchError extends Error
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID < 80000) {
|
||||||
|
class ValueError extends Error
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Symfony\Polyfill\Php80 as p;
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID >= 80000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) {
|
||||||
|
define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('fdiv')) {
|
||||||
|
function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); }
|
||||||
|
}
|
||||||
|
if (!function_exists('preg_last_error_msg')) {
|
||||||
|
function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); }
|
||||||
|
}
|
||||||
|
if (!function_exists('str_contains')) {
|
||||||
|
function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); }
|
||||||
|
}
|
||||||
|
if (!function_exists('str_starts_with')) {
|
||||||
|
function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); }
|
||||||
|
}
|
||||||
|
if (!function_exists('str_ends_with')) {
|
||||||
|
function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); }
|
||||||
|
}
|
||||||
|
if (!function_exists('get_debug_type')) {
|
||||||
|
function get_debug_type($value): string { return p\Php80::get_debug_type($value); }
|
||||||
|
}
|
||||||
|
if (!function_exists('get_resource_id')) {
|
||||||
|
function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); }
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "symfony/polyfill-php80",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
|
||||||
|
"keywords": ["polyfill", "shim", "compatibility", "portable"],
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ion Bazan",
|
||||||
|
"email": "ion.bazan@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": { "Symfony\\Polyfill\\Php80\\": "" },
|
||||||
|
"files": [ "bootstrap.php" ],
|
||||||
|
"classmap": [ "Resources/stubs" ]
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.28-dev"
|
||||||
|
},
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/polyfill",
|
||||||
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
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.
|
@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"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.1.3 || ^8.0",
|
||||||
|
"ext-pcre": "*",
|
||||||
|
"graham-campbell/result-type": "^1.0.2",
|
||||||
|
"phpoption/phpoption": "^1.8",
|
||||||
|
"symfony/polyfill-ctype": "^1.23",
|
||||||
|
"symfony/polyfill-mbstring": "^1.23.1",
|
||||||
|
"symfony/polyfill-php80": "^1.23.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-filter": "*",
|
||||||
|
"bamarni/composer-bin-plugin": "^1.4.1",
|
||||||
|
"phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25"
|
||||||
|
},
|
||||||
|
"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": true
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.5-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,267 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv;
|
||||||
|
|
||||||
|
use Dotenv\Exception\InvalidPathException;
|
||||||
|
use Dotenv\Loader\Loader;
|
||||||
|
use Dotenv\Loader\LoaderInterface;
|
||||||
|
use Dotenv\Parser\Parser;
|
||||||
|
use Dotenv\Parser\ParserInterface;
|
||||||
|
use Dotenv\Repository\Adapter\ArrayAdapter;
|
||||||
|
use Dotenv\Repository\Adapter\PutenvAdapter;
|
||||||
|
use Dotenv\Repository\RepositoryBuilder;
|
||||||
|
use Dotenv\Repository\RepositoryInterface;
|
||||||
|
use Dotenv\Store\StoreBuilder;
|
||||||
|
use Dotenv\Store\StoreInterface;
|
||||||
|
use Dotenv\Store\StringStore;
|
||||||
|
|
||||||
|
class Dotenv
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The store instance.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Store\StoreInterface
|
||||||
|
*/
|
||||||
|
private $store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parser instance.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Parser\ParserInterface
|
||||||
|
*/
|
||||||
|
private $parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The loader instance.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Loader\LoaderInterface
|
||||||
|
*/
|
||||||
|
private $loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The repository instance.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\RepositoryInterface
|
||||||
|
*/
|
||||||
|
private $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new dotenv instance.
|
||||||
|
*
|
||||||
|
* @param \Dotenv\Store\StoreInterface $store
|
||||||
|
* @param \Dotenv\Parser\ParserInterface $parser
|
||||||
|
* @param \Dotenv\Loader\LoaderInterface $loader
|
||||||
|
* @param \Dotenv\Repository\RepositoryInterface $repository
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
StoreInterface $store,
|
||||||
|
ParserInterface $parser,
|
||||||
|
LoaderInterface $loader,
|
||||||
|
RepositoryInterface $repository
|
||||||
|
) {
|
||||||
|
$this->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<string,string|null>
|
||||||
|
*/
|
||||||
|
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<string,string|null>
|
||||||
|
*/
|
||||||
|
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<string,string|null>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Exception;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
interface ExceptionInterface extends Throwable
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Exception;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
final class InvalidEncodingException extends InvalidArgumentException implements ExceptionInterface
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Exception;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
final class InvalidFileException extends InvalidArgumentException implements ExceptionInterface
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Exception;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
final class InvalidPathException extends InvalidArgumentException implements ExceptionInterface
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
final class ValidationException extends RuntimeException implements ExceptionInterface
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Loader;
|
||||||
|
|
||||||
|
use Dotenv\Parser\Entry;
|
||||||
|
use Dotenv\Parser\Value;
|
||||||
|
use Dotenv\Repository\RepositoryInterface;
|
||||||
|
|
||||||
|
final class Loader implements LoaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Load the given entries into the repository.
|
||||||
|
*
|
||||||
|
* We'll substitute any nested variables, and send each variable to the
|
||||||
|
* repository, with the effect of actually mutating the environment.
|
||||||
|
*
|
||||||
|
* @param \Dotenv\Repository\RepositoryInterface $repository
|
||||||
|
* @param \Dotenv\Parser\Entry[] $entries
|
||||||
|
*
|
||||||
|
* @return array<string,string|null>
|
||||||
|
*/
|
||||||
|
public function load(RepositoryInterface $repository, array $entries)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Loader;
|
||||||
|
|
||||||
|
use Dotenv\Repository\RepositoryInterface;
|
||||||
|
|
||||||
|
interface LoaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Load the given entries into the repository.
|
||||||
|
*
|
||||||
|
* @param \Dotenv\Repository\RepositoryInterface $repository
|
||||||
|
* @param \Dotenv\Parser\Entry[] $entries
|
||||||
|
*
|
||||||
|
* @return array<string,string|null>
|
||||||
|
*/
|
||||||
|
public function load(RepositoryInterface $repository, array $entries);
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Loader;
|
||||||
|
|
||||||
|
use Dotenv\Parser\Value;
|
||||||
|
use Dotenv\Repository\RepositoryInterface;
|
||||||
|
use Dotenv\Util\Regex;
|
||||||
|
use Dotenv\Util\Str;
|
||||||
|
use PhpOption\Option;
|
||||||
|
|
||||||
|
final class Resolver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This class is a singleton.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the nested variables in the given value.
|
||||||
|
*
|
||||||
|
* Replaces ${varname} patterns in the allowed positions in the variable
|
||||||
|
* value by an existing environment variable.
|
||||||
|
*
|
||||||
|
* @param \Dotenv\Repository\RepositoryInterface $repository
|
||||||
|
* @param \Dotenv\Parser\Value $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function resolve(RepositoryInterface $repository, Value $value)
|
||||||
|
{
|
||||||
|
return \array_reduce($value->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) {
|
||||||
|
return Option::fromValue($repository->get($matches[1]))
|
||||||
|
->getOrElse($matches[0]);
|
||||||
|
},
|
||||||
|
$str,
|
||||||
|
1
|
||||||
|
)->success()->getOrElse($str);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Parser;
|
||||||
|
|
||||||
|
use PhpOption\Option;
|
||||||
|
|
||||||
|
final class Entry
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The entry name.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entry value.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Parser\Value|null
|
||||||
|
*/
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new entry instance.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param \Dotenv\Parser\Value|null $value
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(string $name, Value $value = null)
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,300 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Parser;
|
||||||
|
|
||||||
|
use Dotenv\Util\Regex;
|
||||||
|
use Dotenv\Util\Str;
|
||||||
|
use GrahamCampbell\ResultType\Error;
|
||||||
|
use GrahamCampbell\ResultType\Result;
|
||||||
|
use GrahamCampbell\ResultType\Success;
|
||||||
|
|
||||||
|
final class EntryParser
|
||||||
|
{
|
||||||
|
private const INITIAL_STATE = 0;
|
||||||
|
private const UNQUOTED_STATE = 1;
|
||||||
|
private const SINGLE_QUOTED_STATE = 2;
|
||||||
|
private const DOUBLE_QUOTED_STATE = 3;
|
||||||
|
private const ESCAPE_SEQUENCE_STATE = 4;
|
||||||
|
private const WHITESPACE_STATE = 5;
|
||||||
|
private const COMMENT_STATE = 6;
|
||||||
|
private const REJECT_STATES = [self::SINGLE_QUOTED_STATE, self::DOUBLE_QUOTED_STATE, self::ESCAPE_SEQUENCE_STATE];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is a singleton.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a raw entry into a proper entry.
|
||||||
|
*
|
||||||
|
* That is, turn a raw environment variable entry into a name and possibly
|
||||||
|
* a value. We wrap the answer in a result type.
|
||||||
|
*
|
||||||
|
* @param string $entry
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry,string>
|
||||||
|
*/
|
||||||
|
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<Value|null,string> */
|
||||||
|
$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<array{string,string|null},string>
|
||||||
|
*/
|
||||||
|
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<array{string,string|null},string> */
|
||||||
|
return Error::create(self::getErrorMessage('an unexpected equals', $line));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,string|null},string> */
|
||||||
|
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<string,string>
|
||||||
|
*/
|
||||||
|
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<string,string> */
|
||||||
|
return Error::create(self::getErrorMessage('an invalid name', $name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<string,string> */
|
||||||
|
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) {
|
||||||
|
/** @psalm-suppress DocblockTypeContradiction */
|
||||||
|
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<array{string,bool,int},string>
|
||||||
|
*/
|
||||||
|
private static function processToken(int $state, string $token)
|
||||||
|
{
|
||||||
|
switch ($state) {
|
||||||
|
case self::INITIAL_STATE:
|
||||||
|
if ($token === '\'') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create(['', false, self::SINGLE_QUOTED_STATE]);
|
||||||
|
} elseif ($token === '"') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create(['', false, self::DOUBLE_QUOTED_STATE]);
|
||||||
|
} elseif ($token === '#') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create(['', false, self::COMMENT_STATE]);
|
||||||
|
} elseif ($token === '$') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create([$token, true, self::UNQUOTED_STATE]);
|
||||||
|
} else {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create([$token, false, self::UNQUOTED_STATE]);
|
||||||
|
}
|
||||||
|
case self::UNQUOTED_STATE:
|
||||||
|
if ($token === '#') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create(['', false, self::COMMENT_STATE]);
|
||||||
|
} elseif (\ctype_space($token)) {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create(['', false, self::WHITESPACE_STATE]);
|
||||||
|
} elseif ($token === '$') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create([$token, true, self::UNQUOTED_STATE]);
|
||||||
|
} else {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create([$token, false, self::UNQUOTED_STATE]);
|
||||||
|
}
|
||||||
|
case self::SINGLE_QUOTED_STATE:
|
||||||
|
if ($token === '\'') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create(['', false, self::WHITESPACE_STATE]);
|
||||||
|
} else {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create([$token, false, self::SINGLE_QUOTED_STATE]);
|
||||||
|
}
|
||||||
|
case self::DOUBLE_QUOTED_STATE:
|
||||||
|
if ($token === '"') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create(['', false, self::WHITESPACE_STATE]);
|
||||||
|
} elseif ($token === '\\') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create(['', false, self::ESCAPE_SEQUENCE_STATE]);
|
||||||
|
} elseif ($token === '$') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create([$token, true, self::DOUBLE_QUOTED_STATE]);
|
||||||
|
} else {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]);
|
||||||
|
}
|
||||||
|
case self::ESCAPE_SEQUENCE_STATE:
|
||||||
|
if ($token === '"' || $token === '\\') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]);
|
||||||
|
} elseif ($token === '$') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
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<array{string,bool,int},string> */
|
||||||
|
return Success::create([\stripcslashes('\\'.$first).Str::substr($token, 1), false, self::DOUBLE_QUOTED_STATE]);
|
||||||
|
} else {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Error::create('an unexpected escape sequence');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case self::WHITESPACE_STATE:
|
||||||
|
if ($token === '#') {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create(['', false, self::COMMENT_STATE]);
|
||||||
|
} elseif (!\ctype_space($token)) {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Error::create('unexpected whitespace');
|
||||||
|
} else {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
return Success::create(['', false, self::WHITESPACE_STATE]);
|
||||||
|
}
|
||||||
|
case self::COMMENT_STATE:
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */
|
||||||
|
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")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Parser;
|
||||||
|
|
||||||
|
final class Lexer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The regex for each type of token.
|
||||||
|
*/
|
||||||
|
private const PATTERNS = [
|
||||||
|
'[\r\n]{1,1000}', '[^\S\r\n]{1,1000}', '\\\\', '\'', '"', '\\#', '\\$', '([^(\s\\\\\'"\\#\\$)]|\\(|\\)){1,1000}',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is a singleton.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert content into a token stream.
|
||||||
|
*
|
||||||
|
* Multibyte string processing is not needed here, and nether is error
|
||||||
|
* handling, for performance reasons.
|
||||||
|
*
|
||||||
|
* @param string $content
|
||||||
|
*
|
||||||
|
* @return \Generator<string>
|
||||||
|
*/
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Parser;
|
||||||
|
|
||||||
|
use Dotenv\Util\Regex;
|
||||||
|
use Dotenv\Util\Str;
|
||||||
|
|
||||||
|
final class Lines
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This class is a singleton.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the array of lines of environment variables.
|
||||||
|
*
|
||||||
|
* This will produce an array of raw entries, one per variable.
|
||||||
|
*
|
||||||
|
* @param string[] $lines
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public static function process(array $lines)
|
||||||
|
{
|
||||||
|
$output = [];
|
||||||
|
$multiline = false;
|
||||||
|
$multilineBuffer = [];
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
[$multiline, $line, $multilineBuffer] = self::multilineProcess($multiline, $line, $multilineBuffer);
|
||||||
|
|
||||||
|
if (!$multiline && !self::isCommentOrWhitespace($line)) {
|
||||||
|
$output[] = $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to make all multiline variable process.
|
||||||
|
*
|
||||||
|
* @param bool $multiline
|
||||||
|
* @param string $line
|
||||||
|
* @param string[] $buffer
|
||||||
|
*
|
||||||
|
* @return array{bool,string,string[]}
|
||||||
|
*/
|
||||||
|
private static function multilineProcess(bool $multiline, string $line, array $buffer)
|
||||||
|
{
|
||||||
|
$startsOnCurrentLine = $multiline ? false : self::looksLikeMultilineStart($line);
|
||||||
|
|
||||||
|
// check if $line can be multiline variable
|
||||||
|
if ($startsOnCurrentLine) {
|
||||||
|
$multiline = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($multiline) {
|
||||||
|
\array_push($buffer, $line);
|
||||||
|
|
||||||
|
if (self::looksLikeMultilineStop($line, $startsOnCurrentLine)) {
|
||||||
|
$multiline = false;
|
||||||
|
$line = \implode("\n", $buffer);
|
||||||
|
$buffer = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$multiline, $line, $buffer];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given line can be the start of a multiline variable.
|
||||||
|
*
|
||||||
|
* @param string $line
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private static function looksLikeMultilineStart(string $line)
|
||||||
|
{
|
||||||
|
return Str::pos($line, '="')->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] === '#');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Parser;
|
||||||
|
|
||||||
|
use Dotenv\Exception\InvalidFileException;
|
||||||
|
use Dotenv\Util\Regex;
|
||||||
|
use GrahamCampbell\ResultType\Result;
|
||||||
|
use GrahamCampbell\ResultType\Success;
|
||||||
|
|
||||||
|
final class Parser implements ParserInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Parse content into an entry array.
|
||||||
|
*
|
||||||
|
* @param string $content
|
||||||
|
*
|
||||||
|
* @throws \Dotenv\Exception\InvalidFileException
|
||||||
|
*
|
||||||
|
* @return \Dotenv\Parser\Entry[]
|
||||||
|
*/
|
||||||
|
public function parse(string $content)
|
||||||
|
{
|
||||||
|
return Regex::split("/(\r\n|\n|\r)/", $content)->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([]));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Parser;
|
||||||
|
|
||||||
|
interface ParserInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Parse content into an entry array.
|
||||||
|
*
|
||||||
|
* @param string $content
|
||||||
|
*
|
||||||
|
* @throws \Dotenv\Exception\InvalidFileException
|
||||||
|
*
|
||||||
|
* @return \Dotenv\Parser\Entry[]
|
||||||
|
*/
|
||||||
|
public function parse(string $content);
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Parser;
|
||||||
|
|
||||||
|
use Dotenv\Util\Str;
|
||||||
|
|
||||||
|
final class Value
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The string representation of the parsed value.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $chars;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The locations of the variables in the value.
|
||||||
|
*
|
||||||
|
* @var int[]
|
||||||
|
*/
|
||||||
|
private $vars;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor for a value.
|
||||||
|
*
|
||||||
|
* @param string $chars
|
||||||
|
* @param int[] $vars
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct(string $chars, array $vars)
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
interface AdapterInterface extends ReaderInterface, WriterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new instance of the adapter, if it is available.
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<\Dotenv\Repository\Adapter\AdapterInterface>
|
||||||
|
*/
|
||||||
|
public static function create();
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
use PhpOption\None;
|
||||||
|
use PhpOption\Option;
|
||||||
|
use PhpOption\Some;
|
||||||
|
|
||||||
|
final class ApacheAdapter implements AdapterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new apache adapter instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of the adapter, if it is available.
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<\Dotenv\Repository\Adapter\AdapterInterface>
|
||||||
|
*/
|
||||||
|
public static function create()
|
||||||
|
{
|
||||||
|
if (self::isSupported()) {
|
||||||
|
/** @var \PhpOption\Option<AdapterInterface> */
|
||||||
|
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<string>
|
||||||
|
*/
|
||||||
|
public function read(string $name)
|
||||||
|
{
|
||||||
|
/** @var \PhpOption\Option<string> */
|
||||||
|
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, '');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
use PhpOption\Option;
|
||||||
|
use PhpOption\Some;
|
||||||
|
|
||||||
|
final class ArrayAdapter implements AdapterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The variables and their values.
|
||||||
|
*
|
||||||
|
* @var array<string,string>
|
||||||
|
*/
|
||||||
|
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<AdapterInterface> */
|
||||||
|
return Some::create(new self());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an environment variable, if it exists.
|
||||||
|
*
|
||||||
|
* @param non-empty-string $name
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<string>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
use PhpOption\Option;
|
||||||
|
use PhpOption\Some;
|
||||||
|
|
||||||
|
final class EnvConstAdapter implements AdapterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new env const adapter instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<AdapterInterface> */
|
||||||
|
return Some::create(new self());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an environment variable, if it exists.
|
||||||
|
*
|
||||||
|
* @param non-empty-string $name
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<string>
|
||||||
|
*/
|
||||||
|
public function read(string $name)
|
||||||
|
{
|
||||||
|
/** @var \PhpOption\Option<string> */
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @psalm-suppress PossiblyInvalidCast */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
final class GuardedWriter implements WriterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The inner writer to use.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\Adapter\WriterInterface
|
||||||
|
*/
|
||||||
|
private $writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The variable name allow list.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $allowList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new guarded writer instance.
|
||||||
|
*
|
||||||
|
* @param \Dotenv\Repository\Adapter\WriterInterface $writer
|
||||||
|
* @param string[] $allowList
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(WriterInterface $writer, array $allowList)
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
final class ImmutableWriter implements WriterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The inner writer to use.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\Adapter\WriterInterface
|
||||||
|
*/
|
||||||
|
private $writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The inner reader to use.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\Adapter\ReaderInterface
|
||||||
|
*/
|
||||||
|
private $reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The record of loaded variables.
|
||||||
|
*
|
||||||
|
* @var array<string,string>
|
||||||
|
*/
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
use PhpOption\None;
|
||||||
|
|
||||||
|
final class MultiReader implements ReaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The set of readers to use.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\Adapter\ReaderInterface[]
|
||||||
|
*/
|
||||||
|
private $readers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new multi-reader instance.
|
||||||
|
*
|
||||||
|
* @param \Dotenv\Repository\Adapter\ReaderInterface[] $readers
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(array $readers)
|
||||||
|
{
|
||||||
|
$this->readers = $readers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an environment variable, if it exists.
|
||||||
|
*
|
||||||
|
* @param non-empty-string $name
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<string>
|
||||||
|
*/
|
||||||
|
public function read(string $name)
|
||||||
|
{
|
||||||
|
foreach ($this->readers as $reader) {
|
||||||
|
$result = $reader->read($name);
|
||||||
|
if ($result->isDefined()) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return None::create();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
final class MultiWriter implements WriterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The set of writers to use.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\Adapter\WriterInterface[]
|
||||||
|
*/
|
||||||
|
private $writers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new multi-writer instance.
|
||||||
|
*
|
||||||
|
* @param \Dotenv\Repository\Adapter\WriterInterface[] $writers
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(array $writers)
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
use PhpOption\None;
|
||||||
|
use PhpOption\Option;
|
||||||
|
use PhpOption\Some;
|
||||||
|
|
||||||
|
final class PutenvAdapter implements AdapterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new putenv adapter instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of the adapter, if it is available.
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<\Dotenv\Repository\Adapter\AdapterInterface>
|
||||||
|
*/
|
||||||
|
public static function create()
|
||||||
|
{
|
||||||
|
if (self::isSupported()) {
|
||||||
|
/** @var \PhpOption\Option<AdapterInterface> */
|
||||||
|
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<string>
|
||||||
|
*/
|
||||||
|
public function read(string $name)
|
||||||
|
{
|
||||||
|
/** @var \PhpOption\Option<string> */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
interface ReaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Read an environment variable, if it exists.
|
||||||
|
*
|
||||||
|
* @param non-empty-string $name
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<string>
|
||||||
|
*/
|
||||||
|
public function read(string $name);
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
final class ReplacingWriter implements WriterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The inner writer to use.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\Adapter\WriterInterface
|
||||||
|
*/
|
||||||
|
private $writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The inner reader to use.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\Adapter\ReaderInterface
|
||||||
|
*/
|
||||||
|
private $reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The record of seen variables.
|
||||||
|
*
|
||||||
|
* @var array<string,string>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
use PhpOption\Option;
|
||||||
|
use PhpOption\Some;
|
||||||
|
|
||||||
|
final class ServerConstAdapter implements AdapterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new server const adapter instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<AdapterInterface> */
|
||||||
|
return Some::create(new self());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an environment variable, if it exists.
|
||||||
|
*
|
||||||
|
* @param non-empty-string $name
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<string>
|
||||||
|
*/
|
||||||
|
public function read(string $name)
|
||||||
|
{
|
||||||
|
/** @var \PhpOption\Option<string> */
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @psalm-suppress PossiblyInvalidCast */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository\Adapter;
|
||||||
|
|
||||||
|
interface WriterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Write to an environment variable, if possible.
|
||||||
|
*
|
||||||
|
* @param non-empty-string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function write(string $name, string $value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an environment variable, if possible.
|
||||||
|
*
|
||||||
|
* @param non-empty-string $name
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function delete(string $name);
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository;
|
||||||
|
|
||||||
|
use Dotenv\Repository\Adapter\ReaderInterface;
|
||||||
|
use Dotenv\Repository\Adapter\WriterInterface;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
final class AdapterRepository implements RepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The reader to use.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\Adapter\ReaderInterface
|
||||||
|
*/
|
||||||
|
private $reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The writer to use.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\Adapter\WriterInterface
|
||||||
|
*/
|
||||||
|
private $writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new adapter repository instance.
|
||||||
|
*
|
||||||
|
* @param \Dotenv\Repository\Adapter\ReaderInterface $reader
|
||||||
|
* @param \Dotenv\Repository\Adapter\WriterInterface $writer
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(ReaderInterface $reader, WriterInterface $writer)
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,272 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository;
|
||||||
|
|
||||||
|
use Dotenv\Repository\Adapter\AdapterInterface;
|
||||||
|
use Dotenv\Repository\Adapter\EnvConstAdapter;
|
||||||
|
use Dotenv\Repository\Adapter\GuardedWriter;
|
||||||
|
use Dotenv\Repository\Adapter\ImmutableWriter;
|
||||||
|
use Dotenv\Repository\Adapter\MultiReader;
|
||||||
|
use Dotenv\Repository\Adapter\MultiWriter;
|
||||||
|
use Dotenv\Repository\Adapter\ReaderInterface;
|
||||||
|
use Dotenv\Repository\Adapter\ServerConstAdapter;
|
||||||
|
use Dotenv\Repository\Adapter\WriterInterface;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use PhpOption\Some;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
final class RepositoryBuilder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The set of default adapters.
|
||||||
|
*/
|
||||||
|
private const DEFAULT_ADAPTERS = [
|
||||||
|
ServerConstAdapter::class,
|
||||||
|
EnvConstAdapter::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of readers to use.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\Adapter\ReaderInterface[]
|
||||||
|
*/
|
||||||
|
private $readers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of writers to use.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\Adapter\WriterInterface[]
|
||||||
|
*/
|
||||||
|
private $writers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are we immutable?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The variable name allow list.
|
||||||
|
*
|
||||||
|
* @var string[]|null
|
||||||
|
*/
|
||||||
|
private $allowList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new repository builder instance.
|
||||||
|
*
|
||||||
|
* @param \Dotenv\Repository\Adapter\ReaderInterface[] $readers
|
||||||
|
* @param \Dotenv\Repository\Adapter\WriterInterface[] $writers
|
||||||
|
* @param bool $immutable
|
||||||
|
* @param string[]|null $allowList
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct(array $readers = [], array $writers = [], bool $immutable = false, array $allowList = null)
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Repository;
|
||||||
|
|
||||||
|
interface RepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the given environment variable is defined.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function has(string $name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an environment variable.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function get(string $name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an environment variable.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function set(string $name, string $value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear an environment variable.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function clear(string $name);
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Store\File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Paths
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This class is a singleton.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the full paths to the files.
|
||||||
|
*
|
||||||
|
* @param string[] $paths
|
||||||
|
* @param string[] $names
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public static function filePaths(array $paths, array $names)
|
||||||
|
{
|
||||||
|
$files = [];
|
||||||
|
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
foreach ($names as $name) {
|
||||||
|
$files[] = \rtrim($path, \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR.$name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Store\File;
|
||||||
|
|
||||||
|
use Dotenv\Exception\InvalidEncodingException;
|
||||||
|
use Dotenv\Util\Str;
|
||||||
|
use PhpOption\Option;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Reader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This class is a singleton.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the file(s), and return their raw content.
|
||||||
|
*
|
||||||
|
* We provide the file path as the key, and its content as the value. If
|
||||||
|
* short circuit mode is enabled, then the returned array with have length
|
||||||
|
* at most one. File paths that couldn't be read are omitted entirely.
|
||||||
|
*
|
||||||
|
* @param string[] $filePaths
|
||||||
|
* @param bool $shortCircuit
|
||||||
|
* @param string|null $fileEncoding
|
||||||
|
*
|
||||||
|
* @throws \Dotenv\Exception\InvalidEncodingException
|
||||||
|
*
|
||||||
|
* @return array<string,string>
|
||||||
|
*/
|
||||||
|
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<string>
|
||||||
|
*/
|
||||||
|
private static function readFromFile(string $path, string $encoding = null)
|
||||||
|
{
|
||||||
|
/** @var Option<string> */
|
||||||
|
$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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Store;
|
||||||
|
|
||||||
|
use Dotenv\Exception\InvalidPathException;
|
||||||
|
use Dotenv\Store\File\Reader;
|
||||||
|
|
||||||
|
final class FileStore implements StoreInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The file paths.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $filePaths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should file loading short circuit?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $shortCircuit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file encoding.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $fileEncoding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new file store instance.
|
||||||
|
*
|
||||||
|
* @param string[] $filePaths
|
||||||
|
* @param bool $shortCircuit
|
||||||
|
* @param string|null $fileEncoding
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(array $filePaths, bool $shortCircuit, string $fileEncoding = null)
|
||||||
|
{
|
||||||
|
$this->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))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Store;
|
||||||
|
|
||||||
|
use Dotenv\Store\File\Paths;
|
||||||
|
|
||||||
|
final class StoreBuilder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The of default name.
|
||||||
|
*/
|
||||||
|
private const DEFAULT_NAME = '.env';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The paths to search within.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file names to search for.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $names;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should file loading short circuit?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $shortCircuit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file encoding.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $fileEncoding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new store builder instance.
|
||||||
|
*
|
||||||
|
* @param string[] $paths
|
||||||
|
* @param string[] $names
|
||||||
|
* @param bool $shortCircuit
|
||||||
|
* @param string|null $fileEncoding
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct(array $paths = [], array $names = [], bool $shortCircuit = false, string $fileEncoding = null)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Store;
|
||||||
|
|
||||||
|
interface StoreInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Read the content of the environment file(s).
|
||||||
|
*
|
||||||
|
* @throws \Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidPathException
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function read();
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Store;
|
||||||
|
|
||||||
|
final class StringStore implements StoreInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The file content.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new string store instance.
|
||||||
|
*
|
||||||
|
* @param string $content
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(string $content)
|
||||||
|
{
|
||||||
|
$this->content = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the content of the environment file(s).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function read()
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Util;
|
||||||
|
|
||||||
|
use GrahamCampbell\ResultType\Error;
|
||||||
|
use GrahamCampbell\ResultType\Success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Regex
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This class is a singleton.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a preg match, wrapping up the result.
|
||||||
|
*
|
||||||
|
* @param string $pattern
|
||||||
|
* @param string $subject
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<bool,string>
|
||||||
|
*/
|
||||||
|
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<int,string>
|
||||||
|
*/
|
||||||
|
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 $callback
|
||||||
|
* @param string $subject
|
||||||
|
* @param int|null $limit
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<string,string>
|
||||||
|
*/
|
||||||
|
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<string[],string>
|
||||||
|
*/
|
||||||
|
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<V,string>
|
||||||
|
*/
|
||||||
|
private static function pregAndWrap(callable $operation, string $subject)
|
||||||
|
{
|
||||||
|
$result = $operation($subject);
|
||||||
|
|
||||||
|
if (\preg_last_error() !== \PREG_NO_ERROR) {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<V,string> */
|
||||||
|
return Error::create(\preg_last_error_msg());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<V,string> */
|
||||||
|
return Success::create($result);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv\Util;
|
||||||
|
|
||||||
|
use GrahamCampbell\ResultType\Error;
|
||||||
|
use GrahamCampbell\ResultType\Success;
|
||||||
|
use PhpOption\Option;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Str
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This class is a singleton.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a string to UTF-8 from the given encoding.
|
||||||
|
*
|
||||||
|
* @param string $input
|
||||||
|
* @param string|null $encoding
|
||||||
|
*
|
||||||
|
* @return \GrahamCampbell\ResultType\Result<string,string>
|
||||||
|
*/
|
||||||
|
public static function utf8(string $input, string $encoding = null)
|
||||||
|
{
|
||||||
|
if ($encoding !== null && !\in_array($encoding, \mb_list_encodings(), true)) {
|
||||||
|
/** @var \GrahamCampbell\ResultType\Result<string,string> */
|
||||||
|
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);
|
||||||
|
/**
|
||||||
|
* 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<string,string> */
|
||||||
|
return Success::create($converted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a given substring of the input.
|
||||||
|
*
|
||||||
|
* @param string $haystack
|
||||||
|
* @param string $needle
|
||||||
|
*
|
||||||
|
* @return \PhpOption\Option<int>
|
||||||
|
*/
|
||||||
|
public static function pos(string $haystack, string $needle)
|
||||||
|
{
|
||||||
|
/** @var \PhpOption\Option<int> */
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,209 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Dotenv;
|
||||||
|
|
||||||
|
use Dotenv\Exception\ValidationException;
|
||||||
|
use Dotenv\Repository\RepositoryInterface;
|
||||||
|
use Dotenv\Util\Regex;
|
||||||
|
use Dotenv\Util\Str;
|
||||||
|
|
||||||
|
class Validator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The environment repository instance.
|
||||||
|
*
|
||||||
|
* @var \Dotenv\Repository\RepositoryInterface
|
||||||
|
*/
|
||||||
|
private $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The variables to validate.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $variables;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new validator instance.
|
||||||
|
*
|
||||||
|
* @param \Dotenv\Repository\RepositoryInterface $repository
|
||||||
|
* @param string[] $variables
|
||||||
|
*
|
||||||
|
* @throws \Dotenv\Exception\ValidationException
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(RepositoryInterface $repository, array $variables)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue