forkbb/app/Core/Config.php

503 lines
15 KiB
PHP
Raw Normal View History

2020-08-06 17:39:33 +00:00
<?php
2020-12-21 10:40:19 +00:00
/**
* This file is part of the ForkBB <https://github.com/forkbb>.
*
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
* @license The MIT License (MIT)
*/
2020-08-06 17:39:33 +00:00
2020-10-14 13:01:43 +00:00
declare(strict_types=1);
2020-08-06 17:39:33 +00:00
namespace ForkBB\Core;
use ForkBB\Core\Exceptions\ForkException;
class Config
{
/**
* Путь до файла конфига
*/
2023-04-27 12:36:15 +00:00
protected string $path;
2020-08-06 17:39:33 +00:00
/**
* Содержимое файла конфига
*/
2023-04-27 12:36:15 +00:00
protected string $fileContents;
2020-08-06 17:39:33 +00:00
/**
* Начальная позиция массива конфига
*/
2023-04-27 12:36:15 +00:00
protected int $arrayStartPos;
2020-08-06 17:39:33 +00:00
/**
2020-08-09 08:53:49 +00:00
* Массив токенов
*/
2023-04-27 12:36:15 +00:00
protected array $tokens;
2020-08-09 08:53:49 +00:00
/**
* Текущая позиция в массиве токенов
2020-08-06 17:39:33 +00:00
*/
2023-04-27 12:36:15 +00:00
protected int $position;
2020-08-09 08:53:49 +00:00
/**
* Массив полученый из файла настройки путем его парсинга
*/
2023-04-27 12:36:15 +00:00
protected array $configArray;
2020-08-09 08:53:49 +00:00
/**
* Строка массива конфига в файле конфигурации
*/
2023-04-27 12:36:15 +00:00
protected string $configStr;
2020-08-06 17:39:33 +00:00
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');
}
2020-08-09 08:53:49 +00:00
$this->fileContents = \file_get_contents($path);
$this->path = $path;
2020-08-06 17:39:33 +00:00
2020-08-09 08:53:49 +00:00
if (\preg_match('%\[\s*\'BASE_URL\'\s+=>%s', $this->fileContents, $matches, \PREG_OFFSET_CAPTURE)) {
$this->arrayStartPos = $matches[0][1];
$this->configArray = $this->getArray();
2020-08-06 17:39:33 +00:00
return;
}
throw new ForkException('The structure of the config file is undefined');
}
/**
2020-08-09 08:53:49 +00:00
* Получает массив настроек из файла конфига
2020-08-06 17:39:33 +00:00
*/
protected function getArray(): array
{
if (
false === \preg_match_all(
'%
//[^\r\n]*+
|
\#[^\r\n]*+
|
/\*.*?\*/
|
\'.*?(?<!\\\\)\'
|
".*?(?<!\\\\)"
|
\s+
|
\[
|
\]
|
,
|
=>
|
function\s*\(.+?\)\s*\{.*?\}(?=,)
2023-03-28 12:12:56 +00:00
|
(?:\\\\)?[\w-]+\s*\(.+?\)(?=,)
|
\S+(?<![,\]\)])
%sx',
2020-08-09 08:53:49 +00:00
\substr($this->fileContents, $this->arrayStartPos),
2020-08-06 17:39:33 +00:00
$matches
)
|| empty($matches)
) {
2023-03-28 12:12:56 +00:00
throw new ForkException('Config array cannot be parsed (1)');
2020-08-06 17:39:33 +00:00
}
2020-08-09 08:53:49 +00:00
$this->tokens = $matches[0];
$this->position = 0;
$this->configStr = '';
2020-08-06 17:39:33 +00:00
return $this->parse('ZERO');
}
2020-08-09 08:53:49 +00:00
/**
* Очищает ключ от кавычек
*/
2023-04-27 12:36:15 +00:00
protected function clearKey(mixed $key): string
2020-08-09 08:53:49 +00:00
{
if (! \is_string($key)) {
2023-03-28 12:12:56 +00:00
throw new ForkException('Config array cannot be parsed (2)');
2020-08-09 08:53:49 +00:00
}
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
2020-08-06 17:39:33 +00:00
{
2020-08-09 08:53:49 +00:00
$result = [];
$value = null;
$key = null;
$other = '';
$value_before = '';
$value_after = '';
$key_before = '';
$key_after = '';
2020-08-06 17:39:33 +00:00
while (isset($this->tokens[$this->position])) {
2020-08-09 08:53:49 +00:00
$token = $this->tokens[$this->position];
$this->configStr .= $token;
2020-08-06 17:39:33 +00:00
// открытие массива
if ('[' === $token) {
switch ($type) {
case 'ZERO':
$type = 'NEW';
break;
case 'NEW':
case '=>':
2020-08-09 09:04:27 +00:00
$this->configStr = \substr($this->configStr, 0, -1);
$value = $this->parse('ZERO');
$value_before = $other;
$other = '';
$type = 'VALUE';
2020-08-06 17:39:33 +00:00
break;
default:
2023-03-28 12:12:56 +00:00
throw new ForkException('Config array cannot be parsed (3)');
2020-08-06 17:39:33 +00:00
}
2020-08-09 08:53:49 +00:00
2020-08-06 17:39:33 +00:00
// закрытие массива
} elseif (']' === $token) {
switch ($type) {
case 'NEW':
case 'VALUE':
case 'VALUE_OR_KEY':
2020-08-09 08:53:49 +00:00
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;
}
2021-12-19 08:31:27 +00:00
} elseif (null !== $key) {
2023-03-28 12:12:56 +00:00
throw new ForkException('Config array cannot be parsed (4)');
2020-08-09 08:53:49 +00:00
}
return $result;
2020-08-06 17:39:33 +00:00
default:
2023-03-28 12:12:56 +00:00
throw new ForkException('Config array cannot be parsed (5)');
2020-08-06 17:39:33 +00:00
}
// новый элемент
} elseif (',' === $token) {
switch ($type) {
case 'VALUE':
case 'VALUE_OR_KEY':
2020-08-09 08:53:49 +00:00
$type = 'NEW';
2020-08-06 17:39:33 +00:00
break;
default:
2023-03-28 12:12:56 +00:00
throw new ForkException('Config array cannot be parsed (6)');
2020-08-06 17:39:33 +00:00
}
// присвоение значения
} elseif ('=>' === $token) {
switch ($type) {
case 'VALUE_OR_KEY':
2020-08-09 08:53:49 +00:00
$key = $value;
$key_before = $value_before;
$key_after = $other;
$other = '';
$value = null;
$value_before = '';
$type = '=>';
2020-08-06 17:39:33 +00:00
break;
default:
2023-03-28 12:12:56 +00:00
throw new ForkException('Config array cannot be parsed (7)');
2020-08-06 17:39:33 +00:00
}
2020-08-09 08:53:49 +00:00
// пробел, комментарий
} elseif (
'' === \trim($token)
|| 0 === \strpos($token, '//')
|| 0 === \strpos($token, '/*')
|| '#' === $token[0]
) {
2020-08-06 17:39:33 +00:00
switch ($type) {
case 'NEW':
case 'VALUE_OR_KEY':
case 'VALUE':
case '=>':
2020-08-09 08:53:49 +00:00
$other .= $token;
2020-08-06 17:39:33 +00:00
break;
default:
2023-03-28 12:12:56 +00:00
throw new ForkException('Config array cannot be parsed (8)');
2020-08-06 17:39:33 +00:00
}
// какое-то значение
} else {
switch ($type) {
case 'NEW':
2020-08-09 08:53:49 +00:00
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;
2021-12-19 08:31:27 +00:00
} elseif (null !== $key) {
2023-03-28 12:12:56 +00:00
throw new ForkException('Config array cannot be parsed (9)');
2020-08-09 08:53:49 +00:00
}
2020-08-06 17:39:33 +00:00
$type = 'VALUE_OR_KEY';
break;
case '=>':
$type = 'VALUE';
break;
default:
2023-03-28 12:12:56 +00:00
throw new ForkException('Config array cannot be parsed (10)');
2020-08-06 17:39:33 +00:00
}
2020-08-09 08:53:49 +00:00
$value = $token;
$value_before = $other;
$other = '';
2020-08-06 17:39:33 +00:00
}
++$this->position;
}
}
2020-08-09 08:53:49 +00:00
2023-04-27 12:36:15 +00:00
protected function isFormat(mixed $data): bool
2020-08-09 08:53:49 +00:00
{
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);
}
/**
2020-08-09 10:42:03 +00:00
* Добавляет/заменяет элемент в конфиг(е)
2020-08-09 08:53:49 +00:00
*/
2023-04-27 12:36:15 +00:00
public function add(string $path, mixed $value, string $after = null): bool
2020-08-09 08:53:49 +00:00
{
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 {
2023-04-28 03:58:24 +00:00
$config[$key] ??= [];
2020-08-09 08:53:49 +00:00
2020-08-09 10:42:03 +00:00
if ($this->isFormat($config[$key])) {
2020-08-09 08:53:49 +00:00
$config = &$config[$key]['value'];
} else {
$config = &$config[$key];
}
}
++$i;
}
$key = $pathArray[$i];
if (
\is_numeric($key) //???? O_o
2022-02-09 14:42:20 +00:00
|| \is_numeric($after) //???? O_o O_o O_o
2020-08-09 08:53:49 +00:00
) {
$config[] = $value;
} elseif (isset($config[$key])) {
if (
$this->isFormat($config[$key])
&& ! $this->isFormat($value)
) {
2020-08-09 08:53:49 +00:00
$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;
}
2020-08-09 10:42:03 +00:00
/**
* Удаляет элемент из конфига
*/
2023-04-27 12:36:15 +00:00
public function delete(string $path): mixed
2020-08-09 10:42:03 +00:00
{
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;
}
}
2020-08-09 08:53:49 +00:00
/**
* Записывает файл конфига с перестройкой массива
*/
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 = '[';
$tail = '';
2020-08-09 08:53:49 +00:00
foreach ($data as $key => $cur) {
$tail = '';
2020-08-09 08:53:49 +00:00
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 = \rtrim($result, "\n\t ");
$result .= "\n{$space}'{$key}' => ";
$tail = "\n" . \str_repeat(' ', $level - 1);
2020-08-09 08:53:49 +00:00
} else {
$result .= ' ';
}
if (\is_array($cur)) {
$result .= $this->toStr($cur, $level + 1) . ',';
} else {
$result .= "{$cur},";
}
}
}
return \rtrim($result . $tail, ',') . ']';
2020-08-09 08:53:49 +00:00
}
2020-08-06 17:39:33 +00:00
}