Passed
Pull Request — 2.x (#135)
by
unknown
20:30
created

JsonExpression::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 11
rs 10
1
<?php
2
3
/**
4
 * This file is part of Cycle ORM package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Database\Injection;
13
14
use Cycle\Database\Driver\CompilerInterface;
15
use Cycle\Database\Driver\Quoter;
16
use Cycle\Database\Exception\DriverException;
17
18
abstract class JsonExpression implements FragmentInterface
19
{
20
    protected string $expression;
21
    protected Quoter $quoter;
22
23
    /**
24
     * @var ParameterInterface[]
25
     */
26
    protected array $parameters = [];
27
28
    /**
29
     * @psalm-param non-empty-string $statement
30
     */
31
    public function __construct(string $statement, mixed ...$parameters)
32
    {
33
        $this->quoter = new Quoter('', $this->getQuotes());
34
35
        $this->expression = $this->compile($statement);
36
37
        foreach ($parameters as $parameter) {
38
            if ($parameter instanceof ParameterInterface) {
39
                $this->parameters[] = $parameter;
40
            } else {
41
                $this->parameters[] = new Parameter($parameter);
42
            }
43
        }
44
    }
45
46
    public function __toString(): string
47
    {
48
        return 'exp:' . $this->expression;
49
    }
50
51
    public static function __set_state(array $an_array): self
52
    {
53
        return new static(
54
            $an_array['expression'] ?? $an_array['statement'],
55
            ...($an_array['parameters'] ?? [])
56
        );
57
    }
58
59
    public function getType(): int
60
    {
61
        return CompilerInterface::JSON_EXPRESSION;
62
    }
63
64
    public function getTokens(): array
65
    {
66
        return [
67
            'expression' => $this->expression,
68
            'parameters' => $this->parameters,
69
        ];
70
    }
71
72
    abstract protected function compile(string $statement): string;
73
74
    /**
75
     * Transforms a string like "options->languages" into a correct path like $."options"."languages".
76
     *
77
     * @param non-empty-string $value
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
78
     * @param non-empty-string $delimiter
79
     *
80
     * @return non-empty-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
81
     */
82
    public function wrapPath(string $value, string $delimiter = '->'): string
83
    {
84
        $value = \preg_replace("/([\\\\]+)?\\'/", "''", $value);
85
86
        $segments = \explode($delimiter, $value);
87
        $path = \implode('.', \array_map(fn (string $segment): string => $this->wrapPathSegment($segment), $segments));
88
89
        return "'$" . (\str_starts_with($path, '[') ? '' : '.') . $path . "'";
90
    }
91
92
    /**
93
     * Parses a string with array access syntax (e.g., "field[array-key]") and extracts the field name and key.
94
     *
95
     * @param non-empty-string $path
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
96
     *
97
     * @return array<non-empty-string>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string>.
Loading history...
98
     */
99
    protected function parseArraySyntax(string $path): array
100
    {
101
        if (\preg_match('/(\[[^\]]+\])+$/', $path, $parts)) {
102
            $parsed = [\trim(\substr($path, 0, \strpos($path, $parts[0])))];
103
104
            \preg_match_all('/\[([^\]]+)\]/', $parts[0], $matches);
105
106
            foreach ($matches[1] as $key) {
107
                if (\trim($key) === '') {
108
                    throw new DriverException('Invalid JSON array path syntax. Array key must not be empty.');
109
                }
110
                $parsed[] = $key;
111
            }
112
113
            return $parsed;
114
        }
115
116
        if (\str_contains($path, '[') && \str_contains($path, ']')) {
117
            throw new DriverException(
118
                'Unable to parse array path syntax. Array key must be wrapped in square brackets.'
119
            );
120
        }
121
122
        return [$path];
123
    }
124
125
    protected function getQuotes(): string
126
    {
127
        return '""';
128
    }
129
130
    /**
131
     * @param non-empty-string $segment
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
132
     *
133
     * @return non-empty-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
134
     */
135
    private function wrapPathSegment(string $segment): string
136
    {
137
        $parts = $this->parseArraySyntax($segment);
138
139
        if (isset($parts[1])) {
140
            return \sprintf('"%s"[%s]', $parts[0], $parts[1]);
141
        }
142
143
        return \sprintf('"%s"', $segment);
144
    }
145
}
146