Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 147 additions & 15 deletions src/FileHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class FileHandler

private array $files = [];


/**
* @throws FileHandlerException
*/
Expand Down Expand Up @@ -160,31 +161,44 @@ public function delete(string $filename): void
/**
* @throws FileHandlerException
*/
private function getRows(): Generator
private function getRows(string|null $filename = null): Generator
{
if (count($this->files) > 1) {
throw new FileHandlerException("multiple files not allowed");
}

$file = $this->files[0];
$headers = fgetcsv($file);

$this->isValidCsvFileFormat($headers);
$file = $this->ensureSingleFileProcessing($filename);
$headers = $this->extractHeader($file);

$isEmptyFile = true;
while (($row = fgetcsv($file)) !== false) {
$isEmptyFile = false;
$this->isValidCsvFileFormat($row);
$item = array_combine($headers, $row);
yield $item;
try {
while (($row = fgetcsv($file)) !== false) {
$isEmptyFile = false;
$this->isValidCsvFileFormat($row);
$item = array_combine($headers, $row);

yield $item;
}
} finally {
fclose($file);
}
fclose($file);


if ($isEmptyFile) {
throw new FileHandlerException('invalid file format');
}
}

private function ensureSingleFileProcessing(string|null $filename): mixed
{
if (count($this->files) < 1) {
if (!$filename || !file_exists($filename)) {
throw new FileHandlerException("no files to process");
}
$this->open($filename);
}
if (count($this->files) > 1) {
throw new FileHandlerException("multiple files not allowed");
}
return $this->files[0];
}

/**
* @throws FileHandlerException
*/
Expand All @@ -198,6 +212,100 @@ private function search(string $keyword, string $column, string|null $format): b
return false;
}

public function findAndReplaceInCsv(
string $filename,
string $keyword,
string $replace,
string|null $column = null
): bool {
$headers = $this->extractHeader($filename);


if (!$headers) {
throw new FileHandlerException('failed to extract header');
}

$tempFilePath = $this->createTempFileWithHeaders($headers);

try {
$count = 0;
foreach ($this->getRows($filename) as $row) {
if (!$column) {
$count += $this->replaceKeywordInRow($row, $keyword, $replace);
} else {
$count += $this->replaceKeywordInColumn($row, $column, $keyword, $replace);
}

$this->writeRowToTempFile($tempFilePath, $row);
}

if ($count < 1) {
return false;
}

$this->renameTempFile($tempFilePath, $filename);
} finally {
$this->cleanupTempFile($tempFilePath);
}

return true;
}

private function replaceKeywordInRow(array &$row, string $keyword, string $replace): int
{
$count = 0;
$replacement = array_search($keyword, $row);

if ($replacement !== false) {
$row[$replacement] = $replace;
$count++;
}

return $count;
}

private function replaceKeywordInColumn(array &$row, string $column, string $keyword, string $replace): int
{
$count = 0;

if ($keyword === $row[$column]) {
$row[$column] = $replace;
$count++;
}

return $count;
}

private function writeRowToTempFile(string $tempFilePath, array $row): void
{
$tempFileHandle = fopen($tempFilePath, 'a');
fputs($tempFileHandle, implode(',', $row) . PHP_EOL);
fclose($tempFileHandle);
}

private function renameTempFile(string $tempFilePath, string $filename): void
{
if (!rename($tempFilePath, $filename)) {
throw new FileHandlerException('Failed to rename temp file');
}
}

private function cleanupTempFile(string $tempFilePath): void
{
unlink($tempFilePath);
}

private function createTempFileWithHeaders(array $headers): string
{
$tempFilePath = tempnam(sys_get_temp_dir(), 'tempfile_');
$tempFileHandle = fopen($tempFilePath, 'w');
fputs($tempFileHandle, implode(',', $headers) . PHP_EOL);
fclose($tempFileHandle);

return $tempFilePath;
}


/**
* @throws FileHandlerException
*/
Expand All @@ -207,4 +315,28 @@ private function isValidCsvFileFormat(array|false $row): void
throw new FileHandlerException('invalid file format');
}
}

private function extractHeader(mixed $file): array|false
{
if (is_resource($file)) {
$headers = fgetcsv($file);
}
if (is_string($file)) {
if (!file_exists($file)) {
return false;
}
try {
$file = fopen($file, 'r');
$headers = fgetcsv($file);
} finally {
fclose($file);
}
}

if ($this->isValidCsvFileFormat($headers) !== false) {
return $headers;
}

return false;
}
}
33 changes: 33 additions & 0 deletions tests/unit/FileHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public function shouldThrowExceptionIfFileIsNotWritable()
$this->expectException(FileHandlerException::class);
$this->expectExceptionMessage('Error writing to file');
$this->fileHandler->write(data: "hello world");
$this->fileHandler->close();
}


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

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


Expand Down Expand Up @@ -252,6 +254,37 @@ public function throwErrorIfFileFormatIsInvalid(string $file)
}
}

#[Test]
public function findAndReplaceInCsvMethodShouldReplaceTextUsingColumnOption()
{
$fileHandler = new FileHandler();

$hasReplaced = $fileHandler->findAndReplaceInCsv("movie.csv", "Twilight", "Inception", "Film");

$this->assertTrue($hasReplaced);


$data = $this->fileHandler->open("movie.csv")->searchInCsvFile("Inception", "Film", FileHandler::ARRAY_FORMAT);

$this->assertEquals($data["Film"], "Inception");
}

#[Test]
public function findAndReplaceInCsvMethodShouldReplaceTextWithoutColumnOption()
{
$fileHandler = new FileHandler();


$hasReplaced = $fileHandler->findAndReplaceInCsv("movie.csv", "Inception", "Twilight");

$this->assertTrue($hasReplaced);


$data = $this->fileHandler->open("movie.csv")->searchInCsvFile("Twilight", "Film", FileHandler::ARRAY_FORMAT);

$this->assertEquals($data["Film"], "Twilight");
}

// Data Providers

public static function provideStudioNames(): iterable
Expand Down