Skip to content

Commit 55db14e

Browse files
authored
find and replace feature (#15)
Signed-off-by: rahul <[email protected]>
1 parent 8d73b3a commit 55db14e

File tree

2 files changed

+180
-15
lines changed

2 files changed

+180
-15
lines changed

src/FileHandler.php

+147-15
Original file line numberDiff line numberDiff line change
@@ -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
}

tests/unit/FileHandlerTest.php

+33
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public function shouldThrowExceptionIfFileIsNotWritable()
8080
$this->expectException(FileHandlerException::class);
8181
$this->expectExceptionMessage('Error writing to file');
8282
$this->fileHandler->write(data: "hello world");
83+
$this->fileHandler->close();
8384
}
8485

8586

@@ -107,6 +108,7 @@ public function multipleFileCanBeWrittenSimultaneously()
107108
$this->assertEquals("hello world", file_get_contents(filename: 'file'));
108109

109110
$this->assertEquals("hello world", file_get_contents(filename: 'file1'));
111+
$this->fileHandler->close();
110112
}
111113

112114

@@ -252,6 +254,37 @@ public function throwErrorIfFileFormatIsInvalid(string $file)
252254
}
253255
}
254256

257+
#[Test]
258+
public function findAndReplaceInCsvMethodShouldReplaceTextUsingColumnOption()
259+
{
260+
$fileHandler = new FileHandler();
261+
262+
$hasReplaced = $fileHandler->findAndReplaceInCsv("movie.csv", "Twilight", "Inception", "Film");
263+
264+
$this->assertTrue($hasReplaced);
265+
266+
267+
$data = $this->fileHandler->open("movie.csv")->searchInCsvFile("Inception", "Film", FileHandler::ARRAY_FORMAT);
268+
269+
$this->assertEquals($data["Film"], "Inception");
270+
}
271+
272+
#[Test]
273+
public function findAndReplaceInCsvMethodShouldReplaceTextWithoutColumnOption()
274+
{
275+
$fileHandler = new FileHandler();
276+
277+
278+
$hasReplaced = $fileHandler->findAndReplaceInCsv("movie.csv", "Inception", "Twilight");
279+
280+
$this->assertTrue($hasReplaced);
281+
282+
283+
$data = $this->fileHandler->open("movie.csv")->searchInCsvFile("Twilight", "Film", FileHandler::ARRAY_FORMAT);
284+
285+
$this->assertEquals($data["Film"], "Twilight");
286+
}
287+
255288
// Data Providers
256289

257290
public static function provideStudioNames(): iterable

0 commit comments

Comments
 (0)