@@ -109,7 +109,7 @@ public function __construct(
109109 string $ glob = "redirect.{csv,tsv,ini} " ,
110110 ?Closure $ redirectHandler = null ,
111111 ) {
112- $ matches = glob ($ glob, GLOB_BRACE ) ?: [] ;
112+ $ matches = $ this -> expandBraceGlob ($ glob) ;
113113 if (count ($ matches ) > 1 ) {
114114 throw new RedirectException ("Multiple redirect files in project root " );
115115 }
@@ -125,7 +125,7 @@ public function __construct(
125125 $ loader = $ this ->createLoader ($ extension );
126126 $ loader ?->load($ this ->redirectFile , $ this ->map );
127127 }
128- }
128+ }
129129
130130 private function createLoader (string $ extension ):?RedirectLoader {
131131 return match ($ extension ) {
@@ -136,6 +136,31 @@ private function createLoader(string $extension):?RedirectLoader {
136136 };
137137 }
138138
139+ /**
140+ * Cross-platform brace expansion for glob patterns.
141+ * Supports a single {a,b,c} segment. Falls back to plain glob when no braces.
142+ * Returns a sorted, unique list of matches.
143+ *
144+ * @return array<int, string>
145+ */
146+ private function expandBraceGlob (string $ pattern ): array {
147+ if (preg_match ('/\{([^}]+)\}/ ' , $ pattern , $ braceMatch )) {
148+ $ options = array_map ('trim ' , explode (', ' , $ braceMatch [1 ]));
149+ $ all = [];
150+ foreach ($ options as $ option ) {
151+ $ subPattern = str_replace ($ braceMatch [0 ], $ option , $ pattern );
152+ $ subMatches = glob ($ subPattern ) ?: [];
153+ if (!empty ($ subMatches )) {
154+ $ all = array_merge ($ all , $ subMatches );
155+ }
156+ }
157+ $ all = array_values (array_unique ($ all ));
158+ sort ($ all );
159+ return $ all ;
160+ }
161+ return glob ($ pattern ) ?: [];
162+ }
163+
139164 public function execute (string $ uri = "/ " ):void {
140165 $ redirect = $ this ->getRedirectUri ($ uri );
141166 if ($ redirect && $ redirect ->code > 0 && $ redirect ->uri !== $ uri ) {
0 commit comments