|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare(strict_types=1); |
|
4
|
|
|
|
|
5
|
|
|
namespace Conia\Quma; |
|
6
|
|
|
|
|
7
|
|
|
use InvalidArgumentException; |
|
8
|
|
|
|
|
9
|
|
|
/** @psalm-api */ |
|
10
|
|
|
class Script |
|
11
|
|
|
{ |
|
12
|
|
|
protected $db; |
|
13
|
|
|
protected $script; |
|
14
|
|
|
protected $isTemplate; |
|
15
|
|
|
|
|
16
|
25 |
|
public function __construct(Database $db, string $script, bool $isTemplate) |
|
17
|
|
|
{ |
|
18
|
25 |
|
$this->db = $db; |
|
19
|
25 |
|
$this->script = $script; |
|
20
|
25 |
|
$this->isTemplate = $isTemplate; |
|
21
|
|
|
} |
|
22
|
|
|
|
|
23
|
1 |
|
public function __invoke(mixed ...$args): Query |
|
24
|
|
|
{ |
|
25
|
1 |
|
return $this->invoke(...$args); |
|
26
|
|
|
} |
|
27
|
|
|
|
|
28
|
25 |
|
public function invoke(mixed ...$argsArray): Query |
|
29
|
|
|
{ |
|
30
|
25 |
|
$args = new Args($argsArray); |
|
31
|
|
|
|
|
32
|
25 |
|
if ($this->isTemplate) { |
|
33
|
5 |
|
if ($args->type() === ArgType::Positional) { |
|
34
|
1 |
|
throw new InvalidArgumentException( |
|
35
|
1 |
|
'Template queries `*.sql.php` allow named parameters only' |
|
36
|
1 |
|
); |
|
37
|
|
|
} |
|
38
|
|
|
|
|
39
|
4 |
|
$script = $this->evaluateTemplate($this->script, $args); |
|
40
|
|
|
|
|
41
|
|
|
// We need to wrap the result of the prepare call in an array |
|
42
|
|
|
// to get back to the format of ...$argsArray. |
|
43
|
4 |
|
$args = new Args([$this->prepareTemplateVars($script, $args)]); |
|
44
|
|
|
} else { |
|
45
|
20 |
|
$script = $this->script; |
|
46
|
|
|
} |
|
47
|
|
|
|
|
48
|
24 |
|
return new Query($this->db, $script, $args); |
|
49
|
|
|
} |
|
50
|
|
|
|
|
51
|
|
|
/** @psalm-suppress PossiblyUnusedParam - $path and $args are used but psalm complains anyway */ |
|
52
|
4 |
|
protected function evaluateTemplate(string $path, Args $args): string |
|
53
|
|
|
{ |
|
54
|
|
|
// Hide $path. Could be overwritten if 'path' exists in $args. |
|
55
|
4 |
|
$____template_path____ = $path; |
|
56
|
4 |
|
unset($path); |
|
57
|
|
|
|
|
58
|
4 |
|
extract(array_merge( |
|
59
|
|
|
// Add the pdo driver to args to allow dynamic |
|
60
|
|
|
// queries based on the platform. |
|
61
|
4 |
|
['pdodriver' => $this->db->getPdoDriver()], |
|
62
|
4 |
|
$args->get() |
|
63
|
4 |
|
)); |
|
64
|
|
|
|
|
65
|
4 |
|
ob_start(); |
|
66
|
|
|
|
|
67
|
|
|
/** @psalm-suppress UnresolvableInclude */ |
|
68
|
4 |
|
include $____template_path____; |
|
69
|
|
|
|
|
70
|
4 |
|
return ob_get_clean(); |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
|
|
/** |
|
74
|
|
|
* Removes all keys from $params which are not present |
|
75
|
|
|
* in the $script. |
|
76
|
|
|
* |
|
77
|
|
|
* PDO does not allow unused parameters. |
|
78
|
|
|
*/ |
|
79
|
4 |
|
protected function prepareTemplateVars(string $script, Args $args): array |
|
80
|
|
|
{ |
|
81
|
|
|
// Remove PostgreSQL blocks |
|
82
|
4 |
|
$script = preg_replace(Query::PATTERN_BLOCK, ' ', $script); |
|
83
|
|
|
// Remove strings |
|
84
|
4 |
|
$script = preg_replace(Query::PATTERN_STRING, ' ', $script); |
|
85
|
|
|
// Remove /* */ comments |
|
86
|
4 |
|
$script = preg_replace(Query::PATTERN_COMMENT_MULTI, ' ', $script); |
|
87
|
|
|
// Remove single line comments |
|
88
|
4 |
|
$script = preg_replace(Query::PATTERN_COMMENT_SINGLE, ' ', $script); |
|
89
|
|
|
|
|
90
|
4 |
|
$newArgs = []; |
|
91
|
|
|
|
|
92
|
|
|
// Match everything starting with : and a letter. |
|
93
|
|
|
// Exclude multiple colons, like type casts (::text). |
|
94
|
|
|
// Would not find a var if it is at the very beginning of script. |
|
95
|
|
|
if ( |
|
96
|
4 |
|
preg_match_all( |
|
97
|
4 |
|
'/[^:]:[a-zA-Z][a-zA-Z0-9_]*/', |
|
98
|
4 |
|
$script, |
|
99
|
4 |
|
$result, |
|
100
|
4 |
|
PREG_PATTERN_ORDER |
|
101
|
4 |
|
) |
|
102
|
|
|
) { |
|
103
|
3 |
|
$argsArray = $args->get(); |
|
104
|
3 |
|
$newArgs = []; |
|
105
|
|
|
|
|
106
|
3 |
|
foreach (array_unique($result[0]) as $arg) { |
|
107
|
3 |
|
$a = substr($arg, 2); |
|
108
|
|
|
assert(!empty($a)); |
|
109
|
|
|
|
|
110
|
|
|
/** @psalm-var array<non-empty-string, mixed> */ |
|
111
|
3 |
|
$newArgs[$a] = $argsArray[$a]; |
|
112
|
|
|
} |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
4 |
|
return $newArgs; |
|
116
|
|
|
} |
|
117
|
|
|
} |
|
118
|
|
|
|