Passed
Push — regex ( b3678c...e53af4 )
by Martin
14:15
created

BaseVariadicFunction::feedParserWithNodes()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 3
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 54
    protected function customizeFunction(): void
23
    {
24 54
        $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 64
    private function feedParserWithNodesForNodeMappingPattern(Parser $parser, string $nodeMappingPattern): void
56
    {
57 64
        $nodeMapping = \explode(',', $nodeMappingPattern);
58 64
        $lexer = $parser->getLexer();
59
60
        try {
61
            // @phpstan-ignore-next-line
62 64
            $this->nodes[] = $parser->{$nodeMapping[0]}();
63 64
            $lookaheadType = DoctrineLexer::getLookaheadType($lexer);
64 64
            if ($lookaheadType === null) {
65 64
                throw InvalidArgumentForVariadicFunctionException::atLeast($this->getFunctionName(), $this->getMinArgumentCount());
66
            }
67
        } catch (\Throwable $throwable) {
68
            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 51
            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
                $this->nodes[] = $parser->{$nodeMapping[$expectedNodeIndex]}(); // @phpstan-ignore-line
89 51
                $nodeIndex++;
90
            }
91
92 51
            $lookaheadType = DoctrineLexer::getLookaheadType($lexer);
93
        }
94
95 50
        $this->validateArguments(...$this->nodes); // @phpstan-ignore-line
96
    }
97
98
    /**
99
     * @throws InvalidArgumentForVariadicFunctionException
100
     */
101 50
    protected function validateArguments(Node ...$arguments): void
102
    {
103 50
        $minArgumentCount = $this->getMinArgumentCount();
104 50
        $maxArgumentCount = $this->getMaxArgumentCount();
105 50
        $argumentCount = \count($arguments);
106
107 50
        if ($argumentCount < $minArgumentCount) {
108 11
            throw InvalidArgumentForVariadicFunctionException::atLeast($this->getFunctionName(), $this->getMinArgumentCount());
109
        }
110
111 39
        if ($argumentCount > $maxArgumentCount) {
112
            throw InvalidArgumentForVariadicFunctionException::between($this->getFunctionName(), $this->getMinArgumentCount(), $this->getMaxArgumentCount());
113
        }
114
    }
115
116 26
    public function getSql(SqlWalker $sqlWalker): string
117
    {
118 26
        $dispatched = [];
119 26
        foreach ($this->nodes as $node) {
120 26
            $dispatched[] = $node instanceof Node ? $node->dispatch($sqlWalker) : 'null';
121
        }
122
123 26
        return \sprintf($this->functionPrototype, \implode(', ', $dispatched));
124
    }
125
}
126