Passed
Push — main ( 7d9f09...bbfba8 )
by Peter
02:36
created

Expr::isParamArray()   B

Complexity

Conditions 9
Paths 5

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 9
c 0
b 0
f 0
nc 5
nop 2
dl 0
loc 19
ccs 10
cts 10
cp 1
crap 9
rs 8.0555
1
<?php
2
3
declare(strict_types=1);
4
5
namespace QB\Generic\Expr;
6
7
use InvalidArgumentException;
8
use PDO;
9
use QB\Generic\IQueryPart;
10
11
class Expr implements IQueryPart
12
{
13
    public const PARAM_ALL_STRING = 1;
14
    public const PARAM_ALL_AUTO   = 2;
15
    public const PARAM_ALL_MANUAL = 4;
16
17
    protected string $sql;
18
19
    protected bool $useNamedParams;
20
21
    /** @var array<int,array<int,mixed>> */
22
    protected array $params = [];
23
24
    /** @var int Helps tracking the extensions done on the SQL originally received */
25
    protected int $extendedBy = 0;
26
27
    /**
28
     * Expr constructor.
29
     *
30
     * @param string|IQueryPart $sql
31
     * @param array             $params
32
     * @param int               $paramHandle
33
     */
34 60
    public function __construct(string|IQueryPart $sql, array $params = [], int $paramHandle = self::PARAM_ALL_AUTO)
35
    {
36 60
        $this->useNamedParams = !array_key_exists(0, $params);
37
38 60
        $this->validateParamHandle($paramHandle);
39 58
        $this->validateParamKeys(array_keys($params));
40
41 56
        $this->sql = (string)$sql;
42
43 56
        $this->bindParams($params, $paramHandle);
44 55
    }
45
46
    /**
47
     * @param int $paramHandle
48
     */
49 60
    protected function validateParamHandle(int $paramHandle): void
50
    {
51 60
        if (!in_array($paramHandle, [self::PARAM_ALL_STRING, self::PARAM_ALL_AUTO, self::PARAM_ALL_MANUAL])) {
52 2
            throw new InvalidArgumentException(
53 2
                sprintf('invalid param handle received: %d.', $paramHandle)
54
            );
55
        }
56 58
    }
57
58
    /**
59
     * @param array $paramKeys
60
     */
61 58
    protected function validateParamKeys(array $paramKeys): void
62
    {
63 58
        if ($this->useNamedParams) {
64 40
            foreach ($paramKeys as $paramKey) {
65 11
                if (is_int($paramKey)) {
66 1
                    throw new InvalidArgumentException(
67 1
                        sprintf('string param key was expected, int received: %d.', $paramKey)
68
                    );
69
                }
70
            }
71
72 39
            return;
73
        }
74
75 28
        $next = 0;
76 28
        foreach ($paramKeys as $paramKey) {
77 28
            if ($paramKey !== $next) {
78 1
                throw new InvalidArgumentException(
79 1
                    sprintf('key was expected to be %d, received: %s.', $next, $paramKey)
80
                );
81
            }
82 28
            $next++;
83
        }
84 27
    }
85
86
    /**
87
     * @param array $params
88
     * @param int   $paramHandle
89
     */
90 56
    protected function bindParams(array $params, int $paramHandle = self::PARAM_ALL_AUTO)
91
    {
92 56
        foreach ($params as $origKey => $origParam) {
93 37
            $fixedParam = $this->toParamArray($origKey, $origParam, $paramHandle);
94 36
            if (count($fixedParam) > 1) {
95 12
                if (!$this->useNamedParams) {
96 6
                    $this->expandSqlUnnamed($origKey, count($fixedParam));
97
                } else {
98 6
                    $this->expandSqlNamed($origKey, count($fixedParam));
99
                }
100
            }
101
102 36
            foreach ($fixedParam as $fixedKey => $var) {
103 36
                if ($this->useNamedParams) {
104 10
                    $this->params[$fixedKey] = $this->getFinalParam($var, $paramHandle);
105
                } else {
106 26
                    $this->params[] = $this->getFinalParam($var, $paramHandle);
107
                }
108
            }
109
        }
110 55
    }
111
112
    /**
113
     * @param string|int $origKey
114
     * @param            $param
115
     * @param int        $paramHandle
116
     *
117
     * @return array
118
     */
119 37
    protected function toParamArray(string|int $origKey, $param, int $paramHandle): array
120
    {
121 37
        if (!$this->isParamArray($param, $paramHandle)) {
122 30
            if ($this->useNamedParams) {
123 7
                return [$origKey => $param];
124
            }
125
126 23
            return [$param];
127
        }
128
129 15
        if (!$this->useNamedParams) {
130 9
            return $param;
131
        }
132
133 6
        $fixedParams = [];
134 6
        foreach ($param as $i => $p) {
135 6
            $fixedParams[$origKey . '_' . $i] = $p;
136
        }
137
138 6
        return $fixedParams;
139
    }
140
141
    /**
142
     * @param     $param
143
     * @param int $paramHandle
144
     *
145
     * @return bool
146
     */
147 37
    protected function isParamArray($param, int $paramHandle): bool
148
    {
149 37
        if (is_scalar($param) || is_null($param)) {
150 26
            return false;
151
        }
152
153 18
        if (!is_array($param)) {
154 1
            throw new InvalidArgumentException(sprintf('param must be scalar or array, %s received.', gettype($param)));
155
        }
156
157 17
        if (in_array($paramHandle, [self::PARAM_ALL_AUTO, self::PARAM_ALL_STRING], true)) {
158 13
            return true;
159
        }
160
161 4
        if (count($param) == 2 && is_scalar($param[0]) && is_int($param[1]) && $param[1] >= 0) {
162 4
            return false;
163
        }
164
165 2
        return true;
166
    }
167
168
    /**
169
     * @param int $origKey
170
     * @param int $fixedParamCount
171
     */
172 6
    protected function expandSqlUnnamed(int $origKey, int $fixedParamCount): void
173
    {
174 6
        $count = $fixedParamCount - 1;
175 6
        $pos   = $origKey + $this->extendedBy + $count;
176
177 6
        $parts = explode('?', $this->sql);
178 6
        $qs    = '?' . str_repeat(', ?', $count);
179 6
        $start = implode('?', array_slice($parts, 0, $pos));
180 6
        $end   = implode('?', array_slice($parts, $pos));
181
182 6
        $this->sql        = $start . $qs . $end;
183 6
        $this->extendedBy += $fixedParamCount - 1;
184 6
    }
185
186
    /**
187
     * @param string $origKey
188
     * @param int    $fixedParamCount
189
     */
190 6
    protected function expandSqlNamed(string $origKey, int $fixedParamCount): void
191
    {
192 6
        $searchKey = ':' . $origKey;
193 6
        $parts     = [];
194 6
        for ($i = 0; $i < $fixedParamCount; $i++) {
195 6
            $parts[] = $searchKey . '_' . $i;
196
        }
197 6
        $replace = implode(', ', $parts);
198
199 6
        $this->sql = str_replace($searchKey, $replace, $this->sql);
200 6
    }
201
202
    /**
203
     * @param     $var
204
     * @param int $paramHandle
205
     *
206
     * @return array
207
     */
208 36
    protected function getFinalParam($var, int $paramHandle): array
209
    {
210
        switch ($paramHandle) {
211 36
            case self::PARAM_ALL_MANUAL:
212 4
                return [$var[0], $var[1]];
213 32
            case self::PARAM_ALL_AUTO:
214 28
                if ($var === null) {
215 1
                    return [$var, PDO::PARAM_NULL];
216 27
                } elseif (is_bool($var)) {
217 2
                    return [$var, PDO::PARAM_BOOL];
218 25
                } elseif (is_int($var)) {
219 11
                    return [$var, PDO::PARAM_INT];
220
                }
221
        }
222
223 24
        return [$var, PDO::PARAM_STR];
224
    }
225
226
    /**
227
     * @return string
228
     */
229 54
    public function __toString(): string
230
    {
231 54
        return $this->sql;
232
    }
233
234
    /**
235
     * @return array
236
     */
237 29
    public function getParams(): array
238
    {
239 29
        return $this->params;
240
    }
241
}
242