@@ -166,9 +166,15 @@ private function exportDocuments(int $batchSize): void
166166 return ;
167167 }
168168
169+ $ this ->validateCSVHeaders ($ headers , $ attributeTypes );
170+
169171 $ buffer = [];
170172
171173 while (($ row = fgetcsv ($ stream )) !== false ) {
174+ if (count ($ row ) !== count ($ headers )) {
175+ throw new \Exception ('CSV row does not match the number of header columns. ' );
176+ }
177+
172178 $ data = array_combine ($ headers , $ row );
173179 if ($ data === false ) {
174180 continue ;
@@ -277,4 +283,34 @@ private function withCsvStream(callable $fn): void
277283 fclose ($ stream );
278284 }
279285 }
286+
287+ /**
288+ * @throws \Exception
289+ */
290+ private function validateCSVHeaders (array $ headers , array $ attributeTypes ): void
291+ {
292+ $ expectedAttributes = array_keys ($ attributeTypes );
293+
294+ // Ignore keys like $id, $permissions, etc.
295+ $ filteredHeaders = array_filter ($ headers , fn ($ key ) => ! str_starts_with ($ key , '$ ' ));
296+
297+ $ extraAttributes = array_diff ($ filteredHeaders , $ expectedAttributes );
298+ $ missingAttributes = array_diff ($ expectedAttributes , $ filteredHeaders );
299+
300+ if (! empty ($ missingAttributes ) || ! empty ($ extraAttributes )) {
301+ $ messages = [];
302+
303+ if (! empty ($ missingAttributes )) {
304+ $ label = count ($ missingAttributes ) === 1 ? 'Missing attribute ' : 'Missing attributes ' ;
305+ $ messages [] = "{$ label }: ' " .implode ("', ' " , $ missingAttributes )."' " ;
306+ }
307+
308+ if (! empty ($ extraAttributes )) {
309+ $ label = count ($ extraAttributes ) === 1 ? 'Unexpected attribute ' : 'Unexpected attributes ' ;
310+ $ messages [] = "{$ label }: ' " .implode ("', ' " , $ extraAttributes )."' " ;
311+ }
312+
313+ throw new \Exception ('CSV header mismatch. ' .implode (' | ' , $ messages ));
314+ }
315+ }
280316}
0 commit comments