Script::evaluateTemplate()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
c 2
b 0
f 0
nc 1
nop 2
dl 0
loc 19
ccs 10
cts 10
cp 1
crap 1
rs 10
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