first commit

This commit is contained in:
Sampanna Rimal
2024-08-27 17:48:06 +05:45
commit 53c0140f58
10839 changed files with 1125847 additions and 0 deletions

View File

@ -0,0 +1,255 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
use Dflydev\DotAccessData\Data;
use Dflydev\DotAccessData\DataInterface;
use Dflydev\DotAccessData\Exception\DataException;
use Dflydev\DotAccessData\Exception\InvalidPathException;
use Dflydev\DotAccessData\Exception\MissingPathException;
use League\Config\Exception\UnknownOptionException;
use League\Config\Exception\ValidationException;
use Nette\Schema\Expect;
use Nette\Schema\Processor;
use Nette\Schema\Schema;
use Nette\Schema\ValidationException as NetteValidationException;
final class Configuration implements ConfigurationBuilderInterface, ConfigurationInterface
{
/** @psalm-readonly */
private Data $userConfig;
/**
* @var array<string, Schema>
*
* @psalm-allow-private-mutation
*/
private array $configSchemas = [];
/** @psalm-allow-private-mutation */
private Data $finalConfig;
/**
* @var array<string, mixed>
*
* @psalm-allow-private-mutation
*/
private array $cache = [];
/** @psalm-readonly */
private ConfigurationInterface $reader;
/**
* @param array<string, Schema> $baseSchemas
*/
public function __construct(array $baseSchemas = [])
{
$this->configSchemas = $baseSchemas;
$this->userConfig = new Data();
$this->finalConfig = new Data();
$this->reader = new ReadOnlyConfiguration($this);
}
/**
* Registers a new configuration schema at the given top-level key
*
* @psalm-allow-private-mutation
*/
public function addSchema(string $key, Schema $schema): void
{
$this->invalidate();
$this->configSchemas[$key] = $schema;
}
/**
* {@inheritDoc}
*
* @psalm-allow-private-mutation
*/
public function merge(array $config = []): void
{
$this->invalidate();
$this->userConfig->import($config, DataInterface::REPLACE);
}
/**
* {@inheritDoc}
*
* @psalm-allow-private-mutation
*/
public function set(string $key, $value): void
{
$this->invalidate();
try {
$this->userConfig->set($key, $value);
} catch (DataException $ex) {
throw new UnknownOptionException($ex->getMessage(), $key, (int) $ex->getCode(), $ex);
}
}
/**
* {@inheritDoc}
*
* @psalm-external-mutation-free
*/
public function get(string $key)
{
if (\array_key_exists($key, $this->cache)) {
return $this->cache[$key];
}
try {
$this->build(self::getTopLevelKey($key));
return $this->cache[$key] = $this->finalConfig->get($key);
} catch (InvalidPathException | MissingPathException $ex) {
throw new UnknownOptionException($ex->getMessage(), $key, (int) $ex->getCode(), $ex);
}
}
/**
* {@inheritDoc}
*
* @psalm-external-mutation-free
*/
public function exists(string $key): bool
{
if (\array_key_exists($key, $this->cache)) {
return true;
}
try {
$this->build(self::getTopLevelKey($key));
return $this->finalConfig->has($key);
} catch (InvalidPathException | UnknownOptionException $ex) {
return false;
}
}
/**
* @psalm-mutation-free
*/
public function reader(): ConfigurationInterface
{
return $this->reader;
}
/**
* @psalm-external-mutation-free
*/
private function invalidate(): void
{
$this->cache = [];
$this->finalConfig = new Data();
}
/**
* Applies the schema against the configuration to return the final configuration
*
* @throws ValidationException|UnknownOptionException|InvalidPathException
*
* @psalm-allow-private-mutation
*/
private function build(string $topLevelKey): void
{
if ($this->finalConfig->has($topLevelKey)) {
return;
}
if (! isset($this->configSchemas[$topLevelKey])) {
throw new UnknownOptionException(\sprintf('Missing config schema for "%s"', $topLevelKey), $topLevelKey);
}
try {
$userData = [$topLevelKey => $this->userConfig->get($topLevelKey)];
} catch (DataException $ex) {
$userData = [];
}
try {
$schema = $this->configSchemas[$topLevelKey];
$processor = new Processor();
$processed = $processor->process(Expect::structure([$topLevelKey => $schema]), $userData);
$this->raiseAnyDeprecationNotices($processor->getWarnings());
$this->finalConfig->import((array) self::convertStdClassesToArrays($processed));
} catch (NetteValidationException $ex) {
throw new ValidationException($ex);
}
}
/**
* Recursively converts stdClass instances to arrays
*
* @phpstan-template T
*
* @param T $data
*
* @return mixed
*
* @phpstan-return ($data is \stdClass ? array<string, mixed> : T)
*
* @psalm-pure
*/
private static function convertStdClassesToArrays($data)
{
if ($data instanceof \stdClass) {
$data = (array) $data;
}
if (\is_array($data)) {
foreach ($data as $k => $v) {
$data[$k] = self::convertStdClassesToArrays($v);
}
}
return $data;
}
/**
* @param string[] $warnings
*/
private function raiseAnyDeprecationNotices(array $warnings): void
{
foreach ($warnings as $warning) {
@\trigger_error($warning, \E_USER_DEPRECATED);
}
}
/**
* @throws InvalidPathException
*/
private static function getTopLevelKey(string $path): string
{
if (\strlen($path) === 0) {
throw new InvalidPathException('Path cannot be an empty string');
}
$path = \str_replace(['.', '/'], '.', $path);
$firstDelimiter = \strpos($path, '.');
if ($firstDelimiter === false) {
return $path;
}
return \substr($path, 0, $firstDelimiter);
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
/**
* Implement this class to facilitate setter injection of the configuration where needed
*/
interface ConfigurationAwareInterface
{
public function setConfiguration(ConfigurationInterface $configuration): void;
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
/**
* An interface that provides the ability to set both the schema and configuration values
*/
interface ConfigurationBuilderInterface extends MutableConfigurationInterface, SchemaBuilderInterface
{
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
use League\Config\Exception\UnknownOptionException;
use League\Config\Exception\ValidationException;
/**
* Interface for reading configuration values
*/
interface ConfigurationInterface
{
/**
* @param string $key Configuration option path/key
*
* @psalm-param non-empty-string $key
*
* @return mixed
*
* @throws ValidationException if the schema failed to validate the given input
* @throws UnknownOptionException if the requested key does not exist or is malformed
*/
public function get(string $key);
/**
* @param string $key Configuration option path/key
*
* @psalm-param non-empty-string $key
*
* @return bool Whether the given option exists
*
* @throws ValidationException if the schema failed to validate the given input
*/
public function exists(string $key): bool;
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
/**
* Interface for a service which provides a readable configuration object
*/
interface ConfigurationProviderInterface
{
public function getConfiguration(): ConfigurationInterface;
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config\Exception;
/**
* Marker interface for any/all exceptions thrown by this library
*/
interface ConfigurationExceptionInterface extends \Throwable
{
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config\Exception;
class InvalidConfigurationException extends \UnexpectedValueException implements ConfigurationExceptionInterface
{
/**
* @param string $option Name/path of the option
* @param mixed $valueGiven The invalid option that was provided
* @param ?string $description Additional text describing the issue (optional)
*/
public static function forConfigOption(string $option, $valueGiven, ?string $description = null): self
{
$message = \sprintf('Invalid config option for "%s": %s', $option, self::getDebugValue($valueGiven));
if ($description !== null) {
$message .= \sprintf(' (%s)', $description);
}
return new self($message);
}
/**
* @param mixed $value
*
* @psalm-pure
*/
private static function getDebugValue($value): string
{
if (\is_object($value)) {
return \get_class($value);
}
return \print_r($value, true);
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config\Exception;
use Throwable;
final class UnknownOptionException extends \InvalidArgumentException implements ConfigurationExceptionInterface
{
private string $path;
public function __construct(string $message, string $path, int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->path = $path;
}
public function getPath(): string
{
return $this->path;
}
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config\Exception;
use Nette\Schema\ValidationException as NetteException;
final class ValidationException extends InvalidConfigurationException
{
/** @var string[] */
private array $messages;
public function __construct(NetteException $innerException)
{
parent::__construct($innerException->getMessage(), (int) $innerException->getCode(), $innerException);
$this->messages = $innerException->getMessages();
}
/**
* @return string[]
*/
public function getMessages(): array
{
return $this->messages;
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
use League\Config\Exception\UnknownOptionException;
/**
* Interface for setting/merging user-defined configuration values into the configuration object
*/
interface MutableConfigurationInterface
{
/**
* @param mixed $value
*
* @throws UnknownOptionException if $key contains a nested path which doesn't point to an array value
*/
public function set(string $key, $value): void;
/**
* @param array<string, mixed> $config
*/
public function merge(array $config = []): void;
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
/**
* Provides read-only access to a given Configuration object
*/
final class ReadOnlyConfiguration implements ConfigurationInterface
{
private Configuration $config;
public function __construct(Configuration $config)
{
$this->config = $config;
}
/**
* {@inheritDoc}
*/
public function get(string $key)
{
return $this->config->get($key);
}
public function exists(string $key): bool
{
return $this->config->exists($key);
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
use Nette\Schema\Schema;
/**
* Interface that allows new schemas to be added to a configuration
*/
interface SchemaBuilderInterface
{
/**
* Registers a new configuration schema at the given top-level key
*/
public function addSchema(string $key, Schema $schema): void;
}