Skip to content

Instantly share code, notes, and snippets.

@maxbin123
Created July 17, 2024 19:14
Show Gist options
  • Save maxbin123/ce72ff3e4a76d79edc6d5f2c9738bae7 to your computer and use it in GitHub Desktop.
Save maxbin123/ce72ff3e4a76d79edc6d5f2c9738bae7 to your computer and use it in GitHub Desktop.
<?php
namespace FpDbTest;
use Exception;
use mysqli;
class Database implements DatabaseInterface
{
private const string SKIP_VALUE = '__SKIP__';
private mysqli $mysqli;
public function __construct(mysqli $mysqli)
{
$this->mysqli = $mysqli;
}
public function buildQuery(string $query, array $args = []): string
{
$index = 0;
$pattern = '/
\{[^{}]*} # Условные блоки: {*******}
| # ИЛИ
\? # Вопрос
(?:
\# # Решетка
| # ИЛИ
\w # буква-цифра
)?
/x';
return preg_replace_callback($pattern, function (array $match) use (&$index, $args): string {
if ($match[0][0] === '{') {
return $this->processConditionalBlock(substr($match[0], 1, -1), $args, $index);
}
$value = $args[$index++];
return $this->formatValue($value, $match[0]);
}, $query);
}
public function skip(): string
{
return self::SKIP_VALUE;
}
private function processConditionalBlock(string $block, array $args, int &$index): string
{
$originalIndex = $index;
$skipBlock = false;
$pattern = '/
\? # Вопрос
(?: #
\# # Решетка
| # ИЛИ
\w # буква-цифра
)?
/x';
$processedBlock = preg_replace_callback($pattern, function (array $match) use (&$index, $args, &$skipBlock): string {
$value = $args[$index++];
if ($value === self::SKIP_VALUE) {
$skipBlock = true;
return '';
}
return $this->formatValue($value, $match[0]);
}, $block);
if ($skipBlock) {
$index = $originalIndex;
return '';
}
return $processedBlock;
}
private function formatValue(mixed $value, string $specifier): string
{
return match ($specifier) {
'?' => $this->format($value),
'?d' => $this->formatInteger($value),
'?f' => $this->formatFloat($value),
'?a' => $this->formatArray($value),
'?#' => $this->formatIdentifier($value),
default => throw new Exception("Unknown specifier: $specifier"),
};
}
private function format(mixed $value): string
{
return match (true) {
is_null($value) => 'NULL',
is_bool($value) => $value ? '1' : '0',
is_int($value) => (string)$value,
is_float($value) => sprintf('%F', $value),
is_string($value) => "'" . $this->escape($value) . "'",
default => throw new Exception("Unsupported type"),
};
}
private function formatInteger(mixed $value): string
{
if (is_null($value)) return 'NULL';
if (is_bool($value)) return $value ? '1' : '0';
return (string)intval($value);
}
private function formatFloat(mixed $value): string
{
if (is_null($value)) return 'NULL';
return sprintf('%F', $value);
}
private function formatArray(mixed $value): string
{
if (empty($value)) return '';
$formattedValues = [];
if (array_is_list($value)) {
foreach ($value as $v) {
$formattedValues[] = is_int($v) ? (string)$v : $this->format($v);
}
} else {
foreach ($value as $k => $v) {
$formattedKey = $this->formatIdentifier($k);
$formattedValue = $this->format($v);
$formattedValues[] = "$formattedKey = $formattedValue";
}
}
return implode(', ', $formattedValues);
}
private function formatIdentifier(mixed $value): string
{
if (is_array($value)) {
$formattedValues = [];
foreach ($value as $v) {
$formattedValues[] = $this->formatIdentifier($v);
}
return implode(', ', $formattedValues);
}
return '`' . str_replace('`', '``', $value) . '`';
}
private function escape(string $value): string
{
return $this->mysqli->real_escape_string($value);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment