Test Failed
Push — renovate/major-composer-qa-too... ( 9595c4...d17b72 )
by
unknown
13:07
created

BaseVariadicFunction::feedParserWithNodes()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 8
ccs 0
cts 5
cp 0
crap 12
rs 10
c 1
b 0
f 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6
7
use Doctrine\ORM\Query\AST\Node;
8
use Doctrine\ORM\Query\Lexer;
9
use Doctrine\ORM\Query\Parser;
10
use Doctrine\ORM\Query\SqlWalker;
11
use Doctrine\ORM\Query\TokenType;
12
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
13
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\ParserException;
14
use MartinGeorgiev\Utils\DoctrineLexer;
15
use MartinGeorgiev\Utils\DoctrineOrm;
16
17
/**
18
 * @author Martin Georgiev <[email protected]>
19
 */
20
abstract class BaseVariadicFunction extends BaseFunction
21
{
22
    protected function customizeFunction(): void
23
    {
24
        $this->setFunctionPrototype(\sprintf('%s(%%s)', $this->getFunctionName()));
25
    }
26
27
    abstract protected function getFunctionName(): string;
28
29
    /**
30
     * @return array<string>
31
     */
32
    abstract protected function getNodeMappingPattern(): array;
33
34
    abstract protected function getMinArgumentCount(): int;
35
36
    abstract protected function getMaxArgumentCount(): int;
37
38
    protected function feedParserWithNodes(Parser $parser): void
39
    {
40
        foreach ($this->getNodeMappingPattern() as $nodeMappingPattern) {
41
            try {
42
                $this->feedParserWithNodesForNodeMappingPattern($parser, $nodeMappingPattern);
43
44
                break;
45
            } catch (ParserException) {
46
                // swallow and continue with next pattern
47
            }
48
        }
49
    }
50
51
    /**
52
     * @throws InvalidArgumentForVariadicFunctionException
53
     * @throws ParserException
54
     */
55
    private function feedParserWithNodesForNodeMappingPattern(Parser $parser, string $nodeMappingPattern): void
56
    {
57
        $nodeMapping = \explode(',', $nodeMappingPattern);
58
        $lexer = $parser->getLexer();
59
60
        try {
61
            $lookaheadType = DoctrineLexer::getLookaheadType($lexer);
62
            if ($lookaheadType === null) {
63
                throw InvalidArgumentForVariadicFunctionException::atLeast($this->getFunctionName(), $this->getMinArgumentCount());
64
            }
65
66
            $this->nodes[] = $parser->{$nodeMapping[0]}(); // @phpstan-ignore-line
67
        } catch (\Throwable $throwable) {
68
            throw ParserException::withThrowable($throwable);
69
        }
70
71
        $shouldUseLexer = DoctrineOrm::isPre219();
72
        $isNodeMappingASimplePattern = \count($nodeMapping) === 1;
73
        $nodeIndex = 1;
74
        while (($shouldUseLexer ? Lexer::T_CLOSE_PARENTHESIS : TokenType::T_CLOSE_PARENTHESIS) !== $lookaheadType) {
0 ignored issues
show
Bug introduced by
The constant Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
75
            if (($shouldUseLexer ? Lexer::T_COMMA : TokenType::T_COMMA) === $lookaheadType) {
0 ignored issues
show
Bug introduced by
The constant Doctrine\ORM\Query\Lexer::T_COMMA was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
76
                $parser->match($shouldUseLexer ? Lexer::T_COMMA : TokenType::T_COMMA);
77
78
                // Check if we're about to exceed the maximum number of arguments
79
                // nodeIndex starts at 1 and counts up for each argument after the first
80
                // So when nodeIndex=1, we're about to add the 2nd argument (total: 2)
81
                // When nodeIndex=2, we're about to add the 3rd argument (total: 3)
82
                $foundMoreNodesThanMappingExpected = ($nodeIndex + 1) > $this->getMaxArgumentCount();
83
                if ($foundMoreNodesThanMappingExpected) {
84
                    throw InvalidArgumentForVariadicFunctionException::between($this->getFunctionName(), $this->getMinArgumentCount(), $this->getMaxArgumentCount());
85
                }
86
87
                $expectedNodeIndex = $isNodeMappingASimplePattern ? 0 : $nodeIndex;
88
                $argumentCountExceedsMappingPatternExpectation = !\array_key_exists($expectedNodeIndex, $nodeMapping);
89
                if ($argumentCountExceedsMappingPatternExpectation) {
90
                    throw InvalidArgumentForVariadicFunctionException::unsupportedCombination(
91
                        $this->getFunctionName(),
92
                        \count($this->nodes) + 1,
93
                        'implementation defines fewer node mappings than the actually provided argument count'
94
                    );
95
                }
96
97
                $this->nodes[] = $parser->{$nodeMapping[$expectedNodeIndex]}(); // @phpstan-ignore-line
98
                $nodeIndex++;
99
            }
100
101
            $lookaheadType = DoctrineLexer::getLookaheadType($lexer);
102
        }
103
104
        // Final validation ensures all arguments meet requirements, including any special rules in subclass implementations
105
        $this->validateArguments(...$this->nodes); // @phpstan-ignore-line
106
    }
107
108
    /**
109
     * @throws InvalidArgumentForVariadicFunctionException
110
     */
111
    protected function validateArguments(Node ...$arguments): void
112
    {
113
        $minArgumentCount = $this->getMinArgumentCount();
114
        $maxArgumentCount = $this->getMaxArgumentCount();
115
        $argumentCount = \count($arguments);
116
117
        if ($argumentCount < $minArgumentCount) {
118
            throw InvalidArgumentForVariadicFunctionException::atLeast($this->getFunctionName(), $this->getMinArgumentCount());
119
        }
120
121
        if ($argumentCount > $maxArgumentCount) {
122
            throw InvalidArgumentForVariadicFunctionException::between($this->getFunctionName(), $this->getMinArgumentCount(), $this->getMaxArgumentCount());
123
        }
124
    }
125
126
    public function getSql(SqlWalker $sqlWalker): string
127
    {
128
        $dispatched = [];
129
        foreach ($this->nodes as $node) {
130
            $dispatched[] = $node instanceof Node ? $node->dispatch($sqlWalker) : 'null';
131
        }
132
133
        return \sprintf($this->functionPrototype, \implode(', ', $dispatched));
134
    }
135
}
136