Skip to content

find and replace feature #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 14, 2023
Merged
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
@@ -13,6 +13,7 @@ class FileHandler

private array $files = [];


/**
* @throws FileHandlerException
*/
@@ -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
*/
@@ -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
*/
@@ -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
@@ -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();
}


@@ -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();
}


@@ -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