@@ -13,6 +13,7 @@ class FileHandler
1313
1414 private array $ files = [];
1515
16+
1617 /**
1718 * @throws FileHandlerException
1819 */
@@ -160,31 +161,44 @@ public function delete(string $filename): void
160161 /**
161162 * @throws FileHandlerException
162163 */
163- private function getRows (): Generator
164+ private function getRows (string | null $ filename = null ): Generator
164165 {
165- if (count ($ this ->files ) > 1 ) {
166- throw new FileHandlerException ("multiple files not allowed " );
167- }
168-
169- $ file = $ this ->files [0 ];
170- $ headers = fgetcsv ($ file );
171-
172- $ this ->isValidCsvFileFormat ($ headers );
166+ $ file = $ this ->ensureSingleFileProcessing ($ filename );
167+ $ headers = $ this ->extractHeader ($ file );
173168
174169 $ isEmptyFile = true ;
175- while (($ row = fgetcsv ($ file )) !== false ) {
176- $ isEmptyFile = false ;
177- $ this ->isValidCsvFileFormat ($ row );
178- $ item = array_combine ($ headers , $ row );
179- yield $ item ;
170+ try {
171+ while (($ row = fgetcsv ($ file )) !== false ) {
172+ $ isEmptyFile = false ;
173+ $ this ->isValidCsvFileFormat ($ row );
174+ $ item = array_combine ($ headers , $ row );
175+
176+ yield $ item ;
177+ }
178+ } finally {
179+ fclose ($ file );
180180 }
181- fclose ( $ file );
181+
182182
183183 if ($ isEmptyFile ) {
184184 throw new FileHandlerException ('invalid file format ' );
185185 }
186186 }
187187
188+ private function ensureSingleFileProcessing (string |null $ filename ): mixed
189+ {
190+ if (count ($ this ->files ) < 1 ) {
191+ if (!$ filename || !file_exists ($ filename )) {
192+ throw new FileHandlerException ("no files to process " );
193+ }
194+ $ this ->open ($ filename );
195+ }
196+ if (count ($ this ->files ) > 1 ) {
197+ throw new FileHandlerException ("multiple files not allowed " );
198+ }
199+ return $ this ->files [0 ];
200+ }
201+
188202 /**
189203 * @throws FileHandlerException
190204 */
@@ -198,6 +212,100 @@ private function search(string $keyword, string $column, string|null $format): b
198212 return false ;
199213 }
200214
215+ public function findAndReplaceInCsv (
216+ string $ filename ,
217+ string $ keyword ,
218+ string $ replace ,
219+ string |null $ column = null
220+ ): bool {
221+ $ headers = $ this ->extractHeader ($ filename );
222+
223+
224+ if (!$ headers ) {
225+ throw new FileHandlerException ('failed to extract header ' );
226+ }
227+
228+ $ tempFilePath = $ this ->createTempFileWithHeaders ($ headers );
229+
230+ try {
231+ $ count = 0 ;
232+ foreach ($ this ->getRows ($ filename ) as $ row ) {
233+ if (!$ column ) {
234+ $ count += $ this ->replaceKeywordInRow ($ row , $ keyword , $ replace );
235+ } else {
236+ $ count += $ this ->replaceKeywordInColumn ($ row , $ column , $ keyword , $ replace );
237+ }
238+
239+ $ this ->writeRowToTempFile ($ tempFilePath , $ row );
240+ }
241+
242+ if ($ count < 1 ) {
243+ return false ;
244+ }
245+
246+ $ this ->renameTempFile ($ tempFilePath , $ filename );
247+ } finally {
248+ $ this ->cleanupTempFile ($ tempFilePath );
249+ }
250+
251+ return true ;
252+ }
253+
254+ private function replaceKeywordInRow (array &$ row , string $ keyword , string $ replace ): int
255+ {
256+ $ count = 0 ;
257+ $ replacement = array_search ($ keyword , $ row );
258+
259+ if ($ replacement !== false ) {
260+ $ row [$ replacement ] = $ replace ;
261+ $ count ++;
262+ }
263+
264+ return $ count ;
265+ }
266+
267+ private function replaceKeywordInColumn (array &$ row , string $ column , string $ keyword , string $ replace ): int
268+ {
269+ $ count = 0 ;
270+
271+ if ($ keyword === $ row [$ column ]) {
272+ $ row [$ column ] = $ replace ;
273+ $ count ++;
274+ }
275+
276+ return $ count ;
277+ }
278+
279+ private function writeRowToTempFile (string $ tempFilePath , array $ row ): void
280+ {
281+ $ tempFileHandle = fopen ($ tempFilePath , 'a ' );
282+ fputs ($ tempFileHandle , implode (', ' , $ row ) . PHP_EOL );
283+ fclose ($ tempFileHandle );
284+ }
285+
286+ private function renameTempFile (string $ tempFilePath , string $ filename ): void
287+ {
288+ if (!rename ($ tempFilePath , $ filename )) {
289+ throw new FileHandlerException ('Failed to rename temp file ' );
290+ }
291+ }
292+
293+ private function cleanupTempFile (string $ tempFilePath ): void
294+ {
295+ unlink ($ tempFilePath );
296+ }
297+
298+ private function createTempFileWithHeaders (array $ headers ): string
299+ {
300+ $ tempFilePath = tempnam (sys_get_temp_dir (), 'tempfile_ ' );
301+ $ tempFileHandle = fopen ($ tempFilePath , 'w ' );
302+ fputs ($ tempFileHandle , implode (', ' , $ headers ) . PHP_EOL );
303+ fclose ($ tempFileHandle );
304+
305+ return $ tempFilePath ;
306+ }
307+
308+
201309 /**
202310 * @throws FileHandlerException
203311 */
@@ -207,4 +315,28 @@ private function isValidCsvFileFormat(array|false $row): void
207315 throw new FileHandlerException ('invalid file format ' );
208316 }
209317 }
318+
319+ private function extractHeader (mixed $ file ): array |false
320+ {
321+ if (is_resource ($ file )) {
322+ $ headers = fgetcsv ($ file );
323+ }
324+ if (is_string ($ file )) {
325+ if (!file_exists ($ file )) {
326+ return false ;
327+ }
328+ try {
329+ $ file = fopen ($ file , 'r ' );
330+ $ headers = fgetcsv ($ file );
331+ } finally {
332+ fclose ($ file );
333+ }
334+ }
335+
336+ if ($ this ->isValidCsvFileFormat ($ headers ) !== false ) {
337+ return $ headers ;
338+ }
339+
340+ return false ;
341+ }
210342}
0 commit comments