Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
19 / 19
QueryParser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
4 / 4
9
100.00% covered (success)
100.00%
19 / 19
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 parseJoin
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 compileJoin
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
6 / 6
 filterArray
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
1<?php declare(strict_types=1);
2/**
3 * Query
4 *
5 * SQL Query Builder / Database Abstraction Layer
6 *
7 * PHP version 7.4
8 *
9 * @package     Query
10 * @author      Timothy J. Warren <tim@timshomepage.net>
11 * @copyright   2012 - 2020 Timothy J. Warren
12 * @license     http://www.opensource.org/licenses/mit-license.html  MIT License
13 * @link        https://git.timshomepage.net/aviat/Query
14 * @version     3.0.0
15 */
16namespace Query;
17
18use Query\Drivers\DriverInterface;
19
20/**
21 * Utility Class to parse sql clauses for properly escaping identifiers
22 */
23class QueryParser {
24
25    /**
26     * DB Driver
27     *
28     * @var DriverInterface
29     */
30    private DriverInterface $db;
31
32    /**
33     * Regex patterns for various syntax components
34     *
35     * @var array
36     */
37    private array $matchPatterns = [
38        'function' => '([a-zA-Z0-9_]+\((.*?)\))',
39        'identifier' => '([a-zA-Z0-9_-]+\.?)+',
40        'operator' => '=|AND|&&?|~|\|\|?|\^|/|>=?|<=?|-|%|OR|\+|NOT|\!=?|<>|XOR'
41    ];
42
43    /**
44     * Regex matches
45     *
46     * @var array
47     */
48    public array $matches = [
49        'functions' => [],
50        'identifiers' => [],
51        'operators' => [],
52        'combined' => [],
53    ];
54
55    /**
56     * Constructor/entry point into parser
57     *
58     * @param DriverInterface $db
59     */
60    public function __construct(DriverInterface $db)
61    {
62        $this->db = $db;
63    }
64
65    /**
66     * Parser method for setting the parse string
67     *
68     * @param string $sql
69     * @return array
70     */
71    public function parseJoin(string $sql): array
72    {
73        // Get sql clause components
74        preg_match_all('`'.$this->matchPatterns['function'].'`', $sql, $this->matches['functions'], PREG_SET_ORDER);
75        preg_match_all('`'.$this->matchPatterns['identifier'].'`', $sql, $this->matches['identifiers'], PREG_SET_ORDER);
76        preg_match_all('`'.$this->matchPatterns['operator'].'`', $sql, $this->matches['operators'], PREG_SET_ORDER);
77
78        // Get everything at once for ordering
79        $fullPattern = '`'.$this->matchPatterns['function'].'+|'.$this->matchPatterns['identifier'].'|('.$this->matchPatterns['operator'].')+`i';
80        preg_match_all($fullPattern, $sql, $this->matches['combined'], PREG_SET_ORDER);
81
82        // Go through the matches, and get the most relevant matches
83        $this->matches = array_map([$this, 'filterArray'], $this->matches);
84
85        return $this->matches;
86    }
87
88    /**
89     * Compiles a join condition after parsing
90     *
91     * @param string $condition
92     * @return string
93     */
94    public function compileJoin(string $condition): string
95    {
96        $parts = $this->parseJoin($condition);
97        $count = count($parts['identifiers']);
98
99        // Go through and quote the identifiers
100        for($i=0; $i <= $count; $i++)
101        {
102            if (in_array($parts['combined'][$i], $parts['identifiers']) && ! is_numeric($parts['combined'][$i]))
103            {
104                $parts['combined'][$i] = $this->db->quoteIdent($parts['combined'][$i]);
105            }
106        }
107
108        return implode('', $parts['combined']);
109    }
110
111    /**
112     * Returns a more useful match array
113     *
114     * @param array $array
115     * @return array
116     */
117    protected function filterArray(array $array): array
118    {
119        $newArray = [];
120
121        foreach($array as $row)
122        {
123            $newArray[] =  (is_array($row)) ? $row[0] : $row;
124        }
125
126        return $newArray;
127    }
128}