Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Doctrine\DBAL\SQL;
4
 
5
use Doctrine\DBAL\SQL\Parser\Exception;
6
use Doctrine\DBAL\SQL\Parser\Exception\RegularExpressionError;
7
use Doctrine\DBAL\SQL\Parser\Visitor;
8
 
9
use function array_merge;
10
use function assert;
11
use function current;
12
use function implode;
13
use function key;
14
use function next;
15
use function preg_last_error;
16
use function preg_match;
17
use function reset;
18
use function sprintf;
19
use function strlen;
20
 
21
use const PREG_NO_ERROR;
22
 
23
/**
24
 * The SQL parser that focuses on identifying prepared statement parameters. It implements parsing other tokens like
25
 * string literals and comments only as a way to not confuse their contents with the the parameter placeholders.
26
 *
27
 * The parsing logic and the implementation is inspired by the PHP PDO parser.
28
 *
29
 * @internal
30
 *
31
 * @see https://github.com/php/php-src/blob/php-7.4.12/ext/pdo/pdo_sql_parser.re#L49-L69
32
 */
33
final class Parser
34
{
35
    private const SPECIAL_CHARS = ':\?\'"`\\[\\-\\/';
36
 
37
    private const BACKTICK_IDENTIFIER  = '`[^`]*`';
38
    private const BRACKET_IDENTIFIER   = '(?<!\b(?i:ARRAY))\[(?:[^\]])*\]';
39
    private const MULTICHAR            = ':{2,}';
40
    private const NAMED_PARAMETER      = ':[a-zA-Z0-9_]+';
41
    private const POSITIONAL_PARAMETER = '(?<!\\?)\\?(?!\\?)';
42
    private const ONE_LINE_COMMENT     = '--[^\r\n]*';
43
    private const MULTI_LINE_COMMENT   = '/\*([^*]+|\*+[^/*])*\**\*/';
44
    private const SPECIAL              = '[' . self::SPECIAL_CHARS . ']';
45
    private const OTHER                = '[^' . self::SPECIAL_CHARS . ']+';
46
 
47
    private string $sqlPattern;
48
 
49
    public function __construct(bool $mySQLStringEscaping)
50
    {
51
        if ($mySQLStringEscaping) {
52
            $patterns = [
53
                $this->getMySQLStringLiteralPattern("'"),
54
                $this->getMySQLStringLiteralPattern('"'),
55
            ];
56
        } else {
57
            $patterns = [
58
                $this->getAnsiSQLStringLiteralPattern("'"),
59
                $this->getAnsiSQLStringLiteralPattern('"'),
60
            ];
61
        }
62
 
63
        $patterns = array_merge($patterns, [
64
            self::BACKTICK_IDENTIFIER,
65
            self::BRACKET_IDENTIFIER,
66
            self::MULTICHAR,
67
            self::ONE_LINE_COMMENT,
68
            self::MULTI_LINE_COMMENT,
69
            self::OTHER,
70
        ]);
71
 
72
        $this->sqlPattern = sprintf('(%s)', implode('|', $patterns));
73
    }
74
 
75
    /**
76
     * Parses the given SQL statement
77
     *
78
     * @throws Exception
79
     */
80
    public function parse(string $sql, Visitor $visitor): void
81
    {
82
        /** @var array<string,callable> $patterns */
83
        $patterns = [
84
            self::NAMED_PARAMETER => static function (string $sql) use ($visitor): void {
85
                $visitor->acceptNamedParameter($sql);
86
            },
87
            self::POSITIONAL_PARAMETER => static function (string $sql) use ($visitor): void {
88
                $visitor->acceptPositionalParameter($sql);
89
            },
90
            $this->sqlPattern => static function (string $sql) use ($visitor): void {
91
                $visitor->acceptOther($sql);
92
            },
93
            self::SPECIAL => static function (string $sql) use ($visitor): void {
94
                $visitor->acceptOther($sql);
95
            },
96
        ];
97
 
98
        $offset = 0;
99
 
100
        while (($handler = current($patterns)) !== false) {
101
            if (preg_match('~\G' . key($patterns) . '~s', $sql, $matches, 0, $offset) === 1) {
102
                $handler($matches[0]);
103
                reset($patterns);
104
 
105
                $offset += strlen($matches[0]);
106
            } elseif (preg_last_error() !== PREG_NO_ERROR) {
107
                // @codeCoverageIgnoreStart
108
                throw RegularExpressionError::new();
109
                // @codeCoverageIgnoreEnd
110
            } else {
111
                next($patterns);
112
            }
113
        }
114
 
115
        assert($offset === strlen($sql));
116
    }
117
 
118
    private function getMySQLStringLiteralPattern(string $delimiter): string
119
    {
120
        return $delimiter . '((\\\\.)|(?![' . $delimiter . '\\\\]).)*' . $delimiter;
121
    }
122
 
123
    private function getAnsiSQLStringLiteralPattern(string $delimiter): string
124
    {
125
        return $delimiter . '[^' . $delimiter . ']*' . $delimiter;
126
    }
127
}