forkbb/app/Core/Config.php
2020-09-12 23:22:32 +07:00

470 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace ForkBB\Core;
use ForkBB\Core\Exceptions\ForkException;
use InvalidArgumentException;
class Config
{
/**
* Путь до файла конфига
* @var string
*/
protected $path;
/**
* Содержимое файла конфига
* @var string
*/
protected $fileContents;
/**
* Начальная позиция массива конфига
* @var int
*/
protected $arrayStartPos;
/**
* Массив токенов
* @var array
*/
protected $tokens;
/**
* Текущая позиция в массиве токенов
* @var int
*/
protected $position;
/**
* Массив полученый из файла настройки путем его парсинга
* @var array
*/
protected $configArray;
/**
* Строка массива конфига в файле конфигурации
* @var string
*/
protected $configStr;
public function __construct(string $path)
{
if (! \is_file($path)) {
throw new ForkException('Config not found');
}
if (! \is_readable($path)) {
throw new ForkException('Config can not be read');
}
if (! \is_writable($path)) {
throw new ForkException('Config can not be write');
}
$this->fileContents = \file_get_contents($path);
$this->path = $path;
if (\preg_match('%\[\s*\'BASE_URL\'\s+=>%s', $this->fileContents, $matches, \PREG_OFFSET_CAPTURE)) {
$this->arrayStartPos = $matches[0][1];
$this->configArray = $this->getArray();
return;
}
throw new ForkException('The structure of the config file is undefined');
}
/**
* Получает массив настроек из файла конфига
*/
protected function getArray(): array
{
if (
false === \preg_match_all(
'%//[^\r\n]*+|#[^\r\n]*+|/\*.*?\*/|\'.*?(?<!\\\\)\'|".*?(?<!\\\\)"|\s+|\[|\]|,|=>|\S+(?<![,\]\)])%s',
\substr($this->fileContents, $this->arrayStartPos),
$matches
)
|| empty($matches)
) {
throw new ForkException('Config array cannot be parsed');
}
$this->tokens = $matches[0];
$this->position = 0;
$this->configStr = '';
return $this->parse('ZERO');
}
/**
* Очищает ключ от кавычек
*/
protected function clearKey(/* mixed */ $key)
{
if (! \is_string($key)) {
throw new ForkException('Config array cannot be parsed');
}
if ((
'\'' === $key[0]
&& \strlen($key) > 1
&& '\'' === $key[-1]
)
|| (
'"' === $key[0]
&& \strlen($key) > 1
&& '"' === $key[-1]
)
) {
return \substr($key, 1, -1);
}
return $key;
}
/**
* Создает массив конфига из токенов (массива подстрок)
*/
protected function parse(string $type): array
{
$result = [];
$value = null;
$key = null;
$other = '';
$value_before = '';
$value_after = '';
$key_before = '';
$key_after = '';
while (isset($this->tokens[$this->position])) {
$token = $this->tokens[$this->position];
$this->configStr .= $token;
// открытие массива
if ('[' === $token) {
switch ($type) {
case 'ZERO':
$type = 'NEW';
break;
case 'NEW':
case '=>':
$this->configStr = \substr($this->configStr, 0, -1);
$value = $this->parse('ZERO');
$value_before = $other;
$other = '';
$type = 'VALUE';
break;
default:
throw new ForkException('Config array cannot be parsed');
}
// закрытие массива
} elseif (']' === $token) {
switch ($type) {
case 'NEW':
case 'VALUE':
case 'VALUE_OR_KEY':
if (null !== $value) {
$value = [
'value' => $value,
'value_before' => $value_before,
'value_after' => $other,
'key_before' => $key_before,
'key_after' => $key_after,
];
if (null !== $key) {
$result[$this->clearKey($key)] = $value;
} else {
$result[] = $value;
}
} elseif (null != $key) {
throw new ForkException('Config array cannot be parsed');
}
return $result;
default:
throw new ForkException('Config array cannot be parsed');
}
// новый элемент
} elseif (',' === $token) {
switch ($type) {
case 'VALUE':
case 'VALUE_OR_KEY':
$type = 'NEW';
break;
default:
throw new ForkException('Config array cannot be parsed');
}
// присвоение значения
} elseif ('=>' === $token) {
switch ($type) {
case 'VALUE_OR_KEY':
$key = $value;
$key_before = $value_before;
$key_after = $other;
$other = '';
$value = null;
$value_before = '';
$type = '=>';
break;
default:
throw new ForkException('Config array cannot be parsed');
}
// пробел, комментарий
} elseif (
'' === \trim($token)
|| 0 === \strpos($token, '//')
|| 0 === \strpos($token, '/*')
|| '#' === $token[0]
) {
switch ($type) {
case 'NEW':
case 'VALUE_OR_KEY':
case 'VALUE':
case '=>':
$other .= $token;
break;
default:
throw new ForkException('Config array cannot be parsed');
}
// какое-то значение
} else {
switch ($type) {
case 'NEW':
if (null !== $value) {
\preg_match('%^([^\r\n]*+)(.*)$%s', $other, $matches);
$value_after = $matches[1];
$other = $matches[2];
$value = [
'value' => $value,
'value_before' => $value_before,
'value_after' => $value_after,
'key_before' => $key_before,
'key_after' => $key_after,
];
$value_before = '';
$value_after = '';
$key_before = '';
$key_after = '';
if (null !== $key) {
$result[$this->clearKey($key)] = $value;
} else {
$result[] = $value;
}
$value = null;
$key = null;
} elseif (null != $key) {
throw new ForkException('Config array cannot be parsed');
}
$type = 'VALUE_OR_KEY';
break;
case '=>':
$type = 'VALUE';
break;
default:
throw new ForkException('Config array cannot be parsed');
}
$value = $token;
$value_before = $other;
$other = '';
}
++$this->position;
}
}
protected function isFormat(/* mixed */ $data): bool
{
return \is_array($data)
&& \array_key_exists('value', $data)
&& \array_key_exists('value_before', $data)
&& \array_key_exists('value_after', $data)
&& \array_key_exists('key_before', $data)
&& \array_key_exists('key_after', $data);
}
/**
* Добавляет/заменяет элемент в конфиг(е)
*/
public function add(string $path, /* mixed */ $value, string $after = null): bool
{
if (empty($this->configArray)) {
$this->configArray = $this->getArray();
}
$pathArray = \explode('=>', $path);
$size = \count($pathArray);
$i = 0;
$config = &$this->configArray;
while ($i < $size - 1) {
$key = $pathArray[$i];
if (\is_numeric($key)) { //???? O_o
$config[] = [];
$config = &$config[\array_key_last($config)];
} else {
if (! isset($config[$key])) {
$config[$key] = [];
}
if ($this->isFormat($config[$key])) {
$config = &$config[$key]['value'];
} else {
$config = &$config[$key];
}
}
++$i;
}
$key = $pathArray[$i];
if (
\is_numeric($key) //???? O_o
|| \is_numeric($after)
) {
$config[] = $value;
} elseif (isset($config[$key])) {
if ($this->isFormat($config[$key])) {
$config[$key]['value'] = $value;
} else {
$config[$key] = $value;
}
} elseif (
null === $after
|| ! isset($config[$after])
) {
$config[$key] = $value;
} else {
$new = [];
foreach ($config as $k => $v) {
if (\is_int($k)) {
$new[] = $v;
} else {
$new[$k] = $v;
if ($k === $after) {
$new[$key] = $value;
}
}
}
$config = $new;
}
return true;
}
/**
* Удаляет элемент из конфига
*/
public function delete(string $path)
{
if (empty($this->configArray)) {
$this->configArray = $this->getArray();
}
$pathArray = \explode('=>', $path);
$size = \count($pathArray);
$i = 0;
$config = &$this->configArray;
while ($i < $size - 1) {
$key = $pathArray[$i];
if (! \array_key_exists($key, $config)) {
return false;
}
if ($this->isFormat($config[$key])) {
$config = &$config[$key]['value'];
} else {
$config = &$config[$key];
}
++$i;
}
$key = $pathArray[$i];
if (! \array_key_exists($key, $config)) {
return false;
} else {
$result = $config[$key];
unset($config[$key]);
return $result;
}
}
/**
* Записывает файл конфига с перестройкой массива
*/
public function save(): void
{
$contents = \str_replace(
$this->configStr,
$this->toStr($this->configArray, 1),
$this->fileContents,
$count
);
if (1 !== $count) {
throw new ForkException('Config array cannot be replace');
}
if (false === \file_put_contents($this->path, $contents, \LOCK_EX)) {
throw new ForkException('Config can not be write');
}
}
/**
* Преобразует массив в строку
*/
protected function toStr(array $data, int $level): string
{
$space = \str_repeat(' ', $level);
$result = '[';
foreach ($data as $key => $cur) {
if ($this->isFormat($cur)) {
if (\is_string($key)) {
$result .= "{$cur['key_before']}'{$key}'{$cur['key_after']}=>{$cur['value_before']}";
} else {
$result .= "{$cur['value_before']}";
}
if (\is_array($cur['value'])) {
$result .= $this->toStr($cur['value'], $level + 1) . ",{$cur['value_after']}";
} else {
$result .= "{$cur['value']},{$cur['value_after']}";
}
} else {
if (\is_string($key)) {
$result .= "\n{$space}'{$key}' => ";
} else {
$result .= ' ';
}
if (\is_array($cur)) {
$result .= $this->toStr($cur, $level + 1) . ',';
} else {
$result .= "{$cur},";
}
}
}
return \rtrim($result, ',') . ']';
}
}