15
15
*/
16
16
final readonly class ImportResolver
17
17
{
18
+ private WildcardPathFinder $ pathFinder ;
19
+
18
20
public function __construct (
19
21
private Directories $ dirs ,
20
22
private FilesInterface $ files ,
21
23
private ConfigLoaderFactory $ loaderFactory ,
22
24
private ?LoggerInterface $ logger = null ,
23
- ) {}
25
+ ) {
26
+ $ this ->pathFinder = new WildcardPathFinder ($ files , $ logger );
27
+ }
24
28
25
29
/**
26
30
* Process imports in a configuration
@@ -49,6 +53,19 @@ public function resolveImports(
49
53
foreach ($ imports as $ importConfig ) {
50
54
$ importCfg = ImportConfig::fromArray ($ importConfig , $ basePath );
51
55
56
+ // Handle wildcard paths
57
+ if ($ importCfg ->hasWildcard ) {
58
+ $ this ->processWildcardImport (
59
+ $ importCfg ,
60
+ $ basePath ,
61
+ $ parsedImports ,
62
+ $ detector ,
63
+ $ importedConfigs ,
64
+ $ importConfig ,
65
+ );
66
+ continue ;
67
+ }
68
+
52
69
// Skip if already processed
53
70
if (\in_array ($ importCfg ->absolutePath , $ parsedImports , true )) {
54
71
$ this ->logger ?->debug('Skipping already processed import ' , [
@@ -58,46 +75,133 @@ public function resolveImports(
58
75
continue ;
59
76
}
60
77
61
- // Check for circular imports
62
- $ detector ->beginProcessing ($ importCfg ->absolutePath );
63
-
64
- try {
65
- // Load the import configuration
66
- $ importedConfig = $ this ->loadImportConfig ($ importCfg );
78
+ // Process a single standard import
79
+ $ this ->processSingleImport (
80
+ $ importCfg ,
81
+ $ basePath ,
82
+ $ parsedImports ,
83
+ $ detector ,
84
+ $ importedConfigs ,
85
+ $ importConfig ,
86
+ );
87
+ }
67
88
68
- // Recursively process nested imports
69
- $ dirname = \dirname ($ importCfg ->absolutePath );
70
- $ importedConfig = $ this ->resolveImports (
71
- $ importedConfig ,
72
- $ dirname ,
73
- $ parsedImports ,
74
- $ detector ,
75
- );
89
+ // Remove the import directive from the original config
90
+ unset($ config ['import ' ]);
76
91
77
- // Apply path prefix if specified
78
- $ importedConfig = $ this ->applyPathPrefix ($ importedConfig , \dirname ((string ) $ importConfig ['path ' ]));
92
+ // Merge all configurations
93
+ return $ this ->mergeConfigurations ([$ config , ...$ importedConfigs ]);
94
+ }
79
95
80
- // Store for later merging
81
- $ importedConfigs [] = $ importedConfig ;
96
+ /**
97
+ * Process a wildcard import pattern
98
+ */
99
+ private function processWildcardImport (
100
+ ImportConfig $ importCfg ,
101
+ string $ basePath ,
102
+ array &$ parsedImports ,
103
+ CircularImportDetector $ detector ,
104
+ array &$ importedConfigs ,
105
+ array $ importConfig ,
106
+ ): void {
107
+ // Find all files that match the pattern
108
+ $ matchingPaths = $ this ->pathFinder ->findMatchingPaths ($ importCfg ->path , $ basePath );
109
+
110
+ if (empty ($ matchingPaths )) {
111
+ $ this ->logger ?->warning('No files match the wildcard pattern ' , [
112
+ 'pattern ' => $ importCfg ->path ,
113
+ 'basePath ' => $ basePath ,
114
+ ]);
115
+ return ;
116
+ }
82
117
83
- // Mark as processed
84
- $ parsedImports [] = $ importCfg ->absolutePath ;
118
+ $ this ->logger ?->debug('Found files matching wildcard pattern ' , [
119
+ 'pattern ' => $ importCfg ->path ,
120
+ 'count ' => \count ($ matchingPaths ),
121
+ 'paths ' => $ matchingPaths ,
122
+ ]);
85
123
86
- $ this ->logger ?->debug('Successfully processed import ' , [
87
- 'path ' => $ importCfg ->path ,
88
- 'absolutePath ' => $ importCfg ->absolutePath ,
124
+ // Process each matching file
125
+ foreach ($ matchingPaths as $ matchingPath ) {
126
+ // Skip if already processed
127
+ if (\in_array ($ matchingPath , $ parsedImports , true )) {
128
+ $ this ->logger ?->debug('Skipping already processed wildcard match ' , [
129
+ 'path ' => $ matchingPath ,
89
130
]);
90
- } finally {
91
- // Always end processing to maintain stack integrity
92
- $ detector ->endProcessing ($ importCfg ->absolutePath );
131
+ continue ;
93
132
}
133
+
134
+ // Create a single import config for this match
135
+ $ singleImportCfg = new ImportConfig (
136
+ path: $ matchingPath , // Use the actual file path
137
+ absolutePath: $ matchingPath , // The path finder returns absolute paths
138
+ pathPrefix: $ importCfg ->pathPrefix ,
139
+ hasWildcard: false ,
140
+ );
141
+
142
+ // Process it using the standard import logic
143
+ $ this ->processSingleImport (
144
+ $ singleImportCfg ,
145
+ \dirname ($ matchingPath ), // Base path is the directory of the matched file
146
+ $ parsedImports ,
147
+ $ detector ,
148
+ $ importedConfigs ,
149
+ $ importConfig ,
150
+ );
94
151
}
152
+ }
95
153
96
- // Remove the import directive from the original config
97
- unset($ config ['import ' ]);
154
+ /**
155
+ * Process a single non-wildcard import
156
+ */
157
+ private function processSingleImport (
158
+ ImportConfig $ importCfg ,
159
+ string $ basePath ,
160
+ array &$ parsedImports ,
161
+ CircularImportDetector $ detector ,
162
+ array &$ importedConfigs ,
163
+ array $ importConfig ,
164
+ ): void {
165
+ // Check for circular imports
166
+ $ detector ->beginProcessing ($ importCfg ->absolutePath );
98
167
99
- // Merge all configurations
100
- return $ this ->mergeConfigurations ([$ config , ...$ importedConfigs ]);
168
+ try {
169
+ // Load the import configuration
170
+ $ importedConfig = $ this ->loadImportConfig ($ importCfg );
171
+
172
+ // Recursively process nested imports
173
+ $ dirname = \dirname ($ importCfg ->absolutePath );
174
+ $ importedConfig = $ this ->resolveImports (
175
+ $ importedConfig ,
176
+ $ dirname ,
177
+ $ parsedImports ,
178
+ $ detector ,
179
+ );
180
+
181
+ // Apply path prefix if specified
182
+ $ importedConfig = $ this ->applyPathPrefix ($ importedConfig , \dirname ((string ) $ importConfig ['path ' ]));
183
+
184
+ // Store for later merging
185
+ $ importedConfigs [] = $ importedConfig ;
186
+
187
+ // Mark as processed
188
+ $ parsedImports [] = $ importCfg ->absolutePath ;
189
+
190
+ $ this ->logger ?->debug('Successfully processed import ' , [
191
+ 'path ' => $ importCfg ->path ,
192
+ 'absolutePath ' => $ importCfg ->absolutePath ,
193
+ ]);
194
+ } catch (\Throwable $ e ) {
195
+ $ this ->logger ?->error('Failed to process import ' , [
196
+ 'path ' => $ importCfg ->path ,
197
+ 'absolutePath ' => $ importCfg ->absolutePath ,
198
+ 'error ' => $ e ->getMessage (),
199
+ ]);
200
+ throw $ e ;
201
+ } finally {
202
+ // Always end processing to maintain stack integrity
203
+ $ detector ->endProcessing ($ importCfg ->absolutePath );
204
+ }
101
205
}
102
206
103
207
/**
0 commit comments