Passed
Push — main ( 6a5ba9...e111dd )
by Martin
12:18
created

BaseVariadicFunction::customizeFunction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 58
    protected function customizeFunction(): void
23
    {
24 58
        $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 64
    protected function feedParserWithNodes(Parser $parser): void
39
    {
40 64
        foreach ($this->getNodeMappingPattern() as $nodeMappingPattern) {
41
            try {
42 64
                $this->feedParserWithNodesForNodeMappingPattern($parser, $nodeMappingPattern);
43
44 26
                break;
45 38
            } catch (ParserException) {
46
                // swallow and continue with next pattern
47
            }
48
        }
49
    }
50
51
    /**
52
     * @throws InvalidArgumentForVariadicFunctionException
53
     * @throws ParserException
54
     */
55 83
    private function feedParserWithNodesForNodeMappingPattern(Parser $parser, string $nodeMappingPattern): void
56
    {
57 83
        $nodeMapping = \explode(',', $nodeMappingPattern);
58 83
        $lexer = $parser->getLexer();
59
60
        try {
61 83
            $lookaheadType = DoctrineLexer::getLookaheadType($lexer);
62 83
            if ($lookaheadType === null) {
63
                throw InvalidArgumentForVariadicFunctionException::atLeast($this->getFunctionName(), $this->getMinArgumentCount());
64
            }
65
66 83
            $this->nodes[] = $parser->{$nodeMapping[0]}(); // @phpstan-ignore-line
67 19
        } catch (\Throwable $throwable) {
68 19
            throw ParserException::withThrowable($throwable);
69
        }
70
71 64
        $shouldUseLexer = DoctrineOrm::isPre219();
72 64
        $isNodeMappingASimplePattern = \count($nodeMapping) === 1;
73 64
        $nodeIndex = 1;
74 64
        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 64
            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 51
                $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 51
                $foundMoreNodesThanMappingExpected = ($nodeIndex + 1) > $this->getMaxArgumentCount();
83 51
                if ($foundMoreNodesThanMappingExpected) {
84 14
                    throw InvalidArgumentForVariadicFunctionException::between($this->getFunctionName(), $this->getMinArgumentCount(), $this->getMaxArgumentCount());
85
                }
86
87 51
                $expectedNodeIndex = $isNodeMappingASimplePattern ? 0 : $nodeIndex;
88 51
                $argumentCountExceedsMappingPatternExpectation = !\array_key_exists($expectedNodeIndex, $nodeMapping);
89 51
                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 51
                $this->nodes[] = $parser->{$nodeMapping[$expectedNodeIndex]}(); // @phpstan-ignore-line
98 51
                $nodeIndex++;
99
            }
100
101 64
            $lookaheadType = DoctrineLexer::getLookaheadType($lexer);
102
        }
103
104
        // Final validation ensures all arguments meet requirements, including any special rules in subclass implementations
105 50
        $this->validateArguments(...$this->nodes); // @phpstan-ignore-line
106
    }
107
108
    /**
109
     * @throws InvalidArgumentForVariadicFunctionException
110
     */
111 50
    protected function validateArguments(Node ...$arguments): void
112
    {
113 50
        $minArgumentCount = $this->getMinArgumentCount();
114 50
        $maxArgumentCount = $this->getMaxArgumentCount();
115 50
        $argumentCount = \count($arguments);
116
117 50
        if ($argumentCount < $minArgumentCount) {
118 11
            throw InvalidArgumentForVariadicFunctionException::atLeast($this->getFunctionName(), $this->getMinArgumentCount());
119
        }
120
121 39
        if ($argumentCount > $maxArgumentCount) {
122
            throw InvalidArgumentForVariadicFunctionException::between($this->getFunctionName(), $this->getMinArgumentCount(), $this->getMaxArgumentCount());
123
        }
124
    }
125
126 26
    public function getSql(SqlWalker $sqlWalker): string
127
    {
128 26
        $dispatched = [];
129 26
        foreach ($this->nodes as $node) {
130 26
            $dispatched[] = $node instanceof Node ? $node->dispatch($sqlWalker) : 'null';
131
        }
132
133 26
        return \sprintf($this->functionPrototype, \implode(', ', $dispatched));
134
    }
135
}
136