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

saygoweb / anorm / 18483020557

14 Oct 2025 01:51AM UTC coverage: 84.208% (+3.7%) from 80.483%
18483020557

Pull #42

github

web-flow
Merge 081631070 into 025025bcf
Pull Request #42: Implement comprehensive join optimization system to solve N+1 query problem

845 of 960 new or added lines in 19 files covered. (88.02%)

66 existing lines in 3 files now uncovered.

1637 of 1944 relevant lines covered (84.21%)

15.67 hits per line

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

84.85
/src/Relationship/BatchLoadingOrchestrator.php
1
<?php
2

3
namespace Anorm\Relationship;
4

5
use Anorm\Relationship\Strategy\QueryStrategySelector;
6
use Anorm\Relationship\Strategy\FieldSelectionParser;
7
use Anorm\Relationship\Strategy\QueryStrategyInterface;
8

9
/**
10
 * Orchestrates batch loading of relationships for multiple models
11
 *
12
 * This class coordinates the loading of relationships across multiple models,
13
 * selecting optimal strategies and managing the batch loading process.
14
 */
15
class BatchLoadingOrchestrator
16
{
17
    /** @var QueryStrategySelector */
18
    private $strategySelector;
19

20
    /** @var FieldSelectionParser */
21
    private $fieldParser;
22

23
    /** @var array Configuration options */
24
    private $config;
25

26
    public function __construct(
27
        QueryStrategySelector $strategySelector = null,
28
        FieldSelectionParser $fieldParser = null,
29
        array $config = []
30
    ) {
31
        $this->strategySelector = $strategySelector ?: new QueryStrategySelector();
104✔
32
        $this->fieldParser = $fieldParser ?: new FieldSelectionParser();
104✔
33
        $this->config = array_merge($this->getDefaultConfig(), $config);
104✔
34
    }
35

36
    /**
37
     * Load relationships for multiple models using optimal strategies
38
     *
39
     * @param array $models Array of model instances
40
     * @param array $relationshipSpecs Array of relationship specifications (e.g., ['posts', 'company:name,address'])
41
     * @return void
42
     */
43
    public function loadRelationshipsForModels(array $models, array $relationshipSpecs): void
44
    {
45
        if (empty($models) || empty($relationshipSpecs)) {
29✔
46
            return;
2✔
47
        }
48

49
        // Parse relationship specifications
50
        $parsedSpecs = $this->fieldParser->parseMultipleSelections($relationshipSpecs);
27✔
51

52
        // Group models by class to handle different model types
53
        $modelsByClass = $this->groupModelsByClass($models);
27✔
54

55
        foreach ($modelsByClass as $modelClass => $classModels) {
27✔
56
            $this->loadRelationshipsForModelClass($classModels, $parsedSpecs);
27✔
57
        }
58
    }
59

60
    /**
61
     * Load relationships for models of a single class
62
     *
63
     * @param array $models Array of model instances of the same class
64
     * @param array $parsedSpecs Parsed relationship specifications
65
     * @return void
66
     */
67
    private function loadRelationshipsForModelClass(array $models, array $parsedSpecs): void
68
    {
69
        if (empty($models)) {
27✔
NEW
70
            return;
×
71
        }
72

73
        // Get relationship manager from first model
74
        $firstModel = reset($models);
27✔
75
        $relationshipManager = $firstModel->getRelationshipManager();
27✔
76

77
        foreach ($parsedSpecs as $relationshipName => $spec) {
27✔
78
            $relationship = $relationshipManager->getRelationship($relationshipName);
27✔
79

80
            if (!$relationship) {
27✔
81
                // Relationship not defined for this model class, skip
82
                continue;
2✔
83
            }
84

85
            $this->loadSingleRelationship($models, $relationship, $spec);
26✔
86
        }
87
    }
88

89
    /**
90
     * Load a single relationship for multiple models
91
     *
92
     * @param array $models Array of model instances
93
     * @param object $relationship The relationship instance
94
     * @param array $spec Parsed relationship specification
95
     * @return void
96
     */
97
    private function loadSingleRelationship(array $models, $relationship, array $spec): void
98
    {
99
        $sourceCount = count($models);
26✔
100
        $fieldSelection = $spec['fields'];
26✔
101

102
        // Select optimal strategy
103
        $strategy = $this->strategySelector->selectStrategy($relationship, $sourceCount, $fieldSelection);
26✔
104

105

106

107
        // Execute the selected strategy
108
        switch ($strategy) {
109
            case QueryStrategyInterface::STRATEGY_IN_CLAUSE_BATCH:
26✔
110
                $this->executeBatchLoading($models, $relationship, $fieldSelection);
5✔
111
                break;
5✔
112

113
            case QueryStrategyInterface::STRATEGY_JOIN_WITH_SELECTION:
21✔
114
                // TODO: Implement JOIN strategy in Phase 3
115
                // For now, fallback to batch loading
NEW
116
                $this->executeBatchLoading($models, $relationship, $fieldSelection);
×
NEW
117
                break;
×
118

119
            case QueryStrategyInterface::STRATEGY_INDIVIDUAL_LOADING:
21✔
120
            default:
121
                $this->executeIndividualLoading($models, $relationship);
21✔
122
                break;
21✔
123
        }
124
    }
125

126
    /**
127
     * Execute batch loading strategy
128
     *
129
     * @param array $models Array of model instances
130
     * @param object $relationship The relationship instance
131
     * @param array|null $fieldSelection Optional field selection for optimization
132
     * @return void
133
     */
134
    private function executeBatchLoading(array $models, $relationship, ?array $fieldSelection = null): void
135
    {
136
        try {
137
            // Get PDO connection from first model
138
            $firstModel = reset($models);
5✔
139
            $pdo = $firstModel->getPdo();
5✔
140

141
            // Load relationships in batch
142
            $batchResults = $relationship->batchLoad($models, $pdo, $fieldSelection);
5✔
143

144
            // Distribute results to models
145
            $relationship->distributeBatchResults($models, $batchResults);
5✔
NEW
146
        } catch (\Exception $e) {
×
147
            // Fallback to individual loading on error
NEW
148
            if ($this->config['debug_mode']) {
×
NEW
149
                error_log("Batch loading failed for relationship {$relationship->getPropertyName()}: " . $e->getMessage());
×
150
            }
NEW
151
            $this->executeIndividualLoading($models, $relationship);
×
152
        }
153
    }
154

155
    /**
156
     * Execute individual loading strategy (fallback)
157
     *
158
     * @param array $models Array of model instances
159
     * @param object $relationship The relationship instance
160
     * @return void
161
     */
162
    private function executeIndividualLoading(array $models, $relationship): void
163
    {
164
        $relationshipName = $relationship->getPropertyName();
21✔
165

166
        foreach ($models as $model) {
21✔
167
            try {
168
                $model->loadRelated($relationshipName);
21✔
NEW
169
            } catch (\Exception $e) {
×
170
                // Log error but continue with other models
NEW
171
                if ($this->config['debug_mode']) {
×
NEW
172
                    error_log("Individual loading failed for model {$model->id}, relationship {$relationshipName}: " . $e->getMessage());
×
173
                }
174
            }
175
        }
176
    }
177

178
    /**
179
     * Group models by their class name
180
     *
181
     * @param array $models Array of model instances
182
     * @return array Models grouped by class name
183
     */
184
    private function groupModelsByClass(array $models): array
185
    {
186
        $grouped = [];
28✔
187

188
        foreach ($models as $model) {
28✔
189
            $className = get_class($model);
28✔
190
            if (!isset($grouped[$className])) {
28✔
191
                $grouped[$className] = [];
28✔
192
            }
193
            $grouped[$className][] = $model;
28✔
194
        }
195

196
        return $grouped;
28✔
197
    }
198

199

200

201
    /**
202
     * Get default configuration options
203
     *
204
     * @return array Default configuration
205
     */
206
    private function getDefaultConfig(): array
207
    {
208
        return [
104✔
209
            'debug_mode' => false,
104✔
210
            'enable_batch_loading' => true,
104✔
211
            'fallback_to_individual' => true,
104✔
212
            'max_batch_size' => 1000,
104✔
213
        ];
104✔
214
    }
215

216
    /**
217
     * Update configuration options
218
     *
219
     * @param array $config Configuration options to merge
220
     * @return void
221
     */
222
    public function setConfig(array $config): void
223
    {
224
        $this->config = array_merge($this->config, $config);
17✔
225
    }
226

227
    /**
228
     * Get current configuration
229
     *
230
     * @return array Current configuration
231
     */
232
    public function getConfig(): array
233
    {
234
        return $this->config;
1✔
235
    }
236

237
    /**
238
     * Get performance statistics for the last batch loading operation
239
     *
240
     * @return array Performance statistics
241
     */
242
    public function getPerformanceStats(): array
243
    {
244
        // TODO: Implement performance tracking
245
        return [
1✔
246
            'total_models_processed' => 0,
1✔
247
            'total_relationships_loaded' => 0,
1✔
248
            'strategies_used' => [],
1✔
249
            'total_queries_executed' => 0,
1✔
250
            'time_elapsed' => 0,
1✔
251
        ];
1✔
252
    }
253
}
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