• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nextras / orm / 27441222947

12 Jun 2026 08:29PM UTC coverage: 91.915% (-0.2%) from 92.162%
27441222947

Pull #810

github

web-flow
Merge 191dbb3fe into 7a20304be
Pull Request #810: Fix DbalCollection::getQueryBuilder()

61 of 66 new or added lines in 3 files covered. (92.42%)

6 existing lines in 3 files now uncovered.

4263 of 4638 relevant lines covered (91.91%)

1.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.15
/src/Collection/Functions/JunctionFunctionTrait.php
1
<?php declare(strict_types = 1);
2

3
namespace Nextras\Orm\Collection\Functions;
4

5

6
use Nextras\Dbal\QueryBuilder\QueryBuilder;
7
use Nextras\Orm\Collection\Aggregations\Aggregator;
8
use Nextras\Orm\Collection\Expression\ExpressionContext;
9
use Nextras\Orm\Collection\Functions\Result\DbalExpressionResult;
10
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
11
use Nextras\Orm\Exception\InvalidStateException;
12
use function array_shift;
13
use function is_int;
14

15

16
/**
17
 * @internal
18
 */
19
trait JunctionFunctionTrait
20
{
21
        /**
22
         * Normalizes directly entered `column => value` expression to a properly structured expression
23
         * array (`['fn-name', ...$args]`). The `column => value` expression is allowed to be combined with another
24
         * argument passed as an implicit AND array.
25
         *
26
         * Example of input:
27
         * ```
28
         * [ICollection::AND, 'id' => 1, ['name' => John]]
29
         * // or
30
         * [IAggregator, ICollection::AND, 'id' => 1, ['name' => John]]
31
         * ```
32
         * is transformed into:
33
         * ```
34
         * [ICollection::AND, [CompareEqualsFunction::class, 'id', 1], ['name' => John]]
35
         * ```
36
         *
37
         * The aggregator is extracted and returned separately.
38
         *
39
         * @param array<mixed> $args
40
         * @return array{list<mixed>, Aggregator<mixed>|null}
41
         */
42
        protected function normalizeFunctions(array $args): array
43
        {
44
                $aggregator = null;
2✔
45
                if (($args[0] ?? null) instanceof Aggregator) {
2✔
46
                        $aggregator = array_shift($args);
2✔
47
                }
48

49
                /** @var array<string, mixed> $args */
50
                $processedArgs = [];
2✔
51
                foreach ($args as $argName => $argValue) {
2✔
52
                        if (is_int($argName)) {
2✔
53
                                // Args passed as array values
54
                                $processedArgs[] = $argValue;
2✔
55
                        } else {
56
                                // Args passed as keys
57
                                $functionCall = $this->conditionParser->parsePropertyOperator($argName);
2✔
58
                                $functionCall[] = $argValue;
2✔
59
                                $processedArgs[] = $functionCall;
2✔
60
                        }
61
                }
62
                return [$processedArgs, $aggregator];
2✔
63
        }
64

65

66
        /**
67
         * @param literal-string $dbalModifier either `%or` or `%and` dbal modifier
68
         * @param array<mixed> $args
69
         * @param Aggregator<mixed>|null $aggregator
70
         */
71
        protected function processQueryBuilderExpressionWithModifier(
72
                string $dbalModifier,
73
                DbalQueryBuilderHelper $helper,
74
                QueryBuilder $builder,
75
                array $args,
76
                ?Aggregator $aggregator,
77
        ): DbalExpressionResult
78
        {
79
                [$normalized, $newAggregator] = $this->normalizeFunctions($args);
2✔
80
                if ($newAggregator !== null) {
2✔
81
                        if ($aggregator !== null) throw new InvalidStateException("Cannot apply two aggregations simultaneously.");
2✔
82
                        $aggregator = $newAggregator;
2✔
83
                }
84

85
                $requiresHaving = false;
2✔
86
                $expressions = [];
2✔
87
                foreach ($normalized as $collectionFunctionArgs) {
2✔
88
                        $expressions[] = $expression = $helper->processExpression($builder, $collectionFunctionArgs, $aggregator);
2✔
89
                        if ($expression->havingExpression !== null || ($expression->aggregator?->isHavingClauseRequired() ?? false)) {
2✔
90
                                $requiresHaving = true;
2✔
91
                        }
92
                }
93

94
                return new DbalExpressionResult(
2✔
95
                        expression: $dbalModifier,
96
                        args: $expressions,
97
                        havingExpression: $requiresHaving ? $dbalModifier : null,
2✔
98
                        collectCallback: function (ExpressionContext $context) use ($helper, $dbalModifier) {
2✔
99
                                /** @var DbalExpressionResult $this */
100

101
                                $processedArgs = [];
2✔
102
                                $processedHavingArgs = [];
2✔
103
                                $joins = [];
2✔
104
                                $groupBy = [];
2✔
105
                                $columns = [];
2✔
106

107
                                if ($dbalModifier === '%or') {
2✔
108
                                        if ($context === ExpressionContext::FilterAnd) {
2✔
109
                                                $finalContext = ExpressionContext::FilterOr;
2✔
110
                                        } elseif ($context === ExpressionContext::FilterAndWithHavingClause) {
2✔
111
                                                $finalContext = ExpressionContext::FilterOrWithHavingClause;
2✔
112
                                        } else {
UNCOV
113
                                                $finalContext = $context;
×
114
                                        }
115
                                } else {
116
                                        $finalContext = $context;
2✔
117
                                }
118

119
                                foreach ($this->args as $arg) {
2✔
120
                                        assert($arg instanceof DbalExpressionResult);
121
                                        $expression = $arg->collect($finalContext)->applyAggregator($finalContext);
2✔
122

123
                                        $whereArgs = $expression->getArgsForExpansion();
2✔
124
                                        if ($whereArgs !== []) {
2✔
125
                                                $processedArgs[] = $whereArgs;
2✔
126
                                        }
127
                                        $havingArgs = $expression->getHavingArgsForExpansion();
2✔
128
                                        if ($havingArgs !== []) {
2✔
129
                                                $processedHavingArgs[] = $havingArgs;
2✔
130
                                        }
131
                                        $joins = array_merge($joins, $expression->joins);
2✔
132
                                        $groupBy = array_merge($groupBy, $expression->groupBy);
2✔
133
                                        $columns = array_merge($columns, $expression->columns);
2✔
134
                                }
135

136
                                return new DbalExpressionResult(
2✔
137
                                        expression: $processedArgs === [] ? null : $dbalModifier,
2✔
138
                                        args: $processedArgs === [] ? [] : [$processedArgs],
2✔
139
                                        joins: $helper->mergeJoins($dbalModifier, $joins),
2✔
140
                                        groupBy: $groupBy,
141
                                        havingExpression: $processedHavingArgs === [] ? null : $dbalModifier,
2✔
142
                                        havingArgs: $processedHavingArgs === [] ? [] : [$processedHavingArgs],
2✔
143
                                        columns: $columns,
144
                                );
145
                        },
2✔
146
                );
147
        }
148
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc