Completed
Push — 2.x ( 6c47ee...c394f3 )
by Aleksei
24s queued 15s
created

JsonExpression::parseArraySyntax()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 12
nc 5
nop 1
dl 0
loc 24
rs 9.2222
c 0
b 0
f 0
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
    /**
73
     * @param non-empty-string $statement
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...
74
     *
75
     * @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...
76
     */
77
    abstract protected function compile(string $statement): string;
78
79
    /**
80
     * @param non-empty-string $statement
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
     * @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...
83
     */
84
    protected function getField(string $statement): string
85
    {
86
        $parts = \explode('->', $statement, 2);
87
88
        return $this->quoter->quote($parts[0]);
89
    }
90
91
    /**
92
     * @param non-empty-string $statement
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...
93
     *
94
     * @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...
95
     */
96
    protected function getPath(string $statement): string
97
    {
98
        $parts = \explode('->', $statement, 2);
99
100
        return \count($parts) > 1 ? ', ' . $this->wrapPath($parts[1]) : '';
101
    }
102
103
    /**
104
     * Parses a string with array access syntax (e.g., "field[array-key]") and extracts the field name and key.
105
     *
106
     * @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...
107
     *
108
     * @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...
109
     */
110
    protected function parseArraySyntax(string $path): array
111
    {
112
        if (\preg_match('/(\[[^\]]+\])+$/', $path, $parts)) {
113
            $parsed = [\trim(\substr($path, 0, \strpos($path, $parts[0])))];
114
115
            \preg_match_all('/\[([^\]]+)\]/', $parts[0], $matches);
116
117
            foreach ($matches[1] as $key) {
118
                if (\trim($key) === '') {
119
                    throw new DriverException('Invalid JSON array path syntax. Array key must not be empty.');
120
                }
121
                $parsed[] = $key;
122
            }
123
124
            return $parsed;
125
        }
126
127
        if (\str_contains($path, '[') && \str_contains($path, ']')) {
128
            throw new DriverException(
129
                'Unable to parse array path syntax. Array key must be wrapped in square brackets.'
130
            );
131
        }
132
133
        return [$path];
134
    }
135
136
    /**
137
     * @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...
138
     */
139
    protected function getQuotes(): string
140
    {
141
        return '""';
142
    }
143
144
    /**
145
     * Transforms a string like "options->languages" into a correct path like $."options"."languages".
146
     *
147
     * @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...
148
     * @param non-empty-string $delimiter
149
     *
150
     * @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...
151
     */
152
    private function wrapPath(string $value, string $delimiter = '->'): string
153
    {
154
        $value = \preg_replace("/(\\+)?'/", "''", $value);
155
156
        $segments = \explode($delimiter, $value);
157
        $path = \implode('.', \array_map(fn (string $segment): string => $this->wrapPathSegment($segment), $segments));
158
159
        return "'$" . (\str_starts_with($path, '[') ? '' : '.') . $path . "'";
160
    }
161
162
    /**
163
     * @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...
164
     *
165
     * @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...
166
     */
167
    private function wrapPathSegment(string $segment): string
168
    {
169
        $parts = $this->parseArraySyntax($segment);
170
171
        if (isset($parts[1])) {
172
            return \sprintf('"%s"[%s]', $parts[0], $parts[1]);
173
        }
174
175
        return \sprintf('"%s"', $segment);
176
    }
177
}
178