Console Tool Refactoring

This commit is contained in:
2023-07-26 18:21:51 +02:00
parent b5836f4ae9
commit 10d7b09c5f
36 changed files with 1605 additions and 427 deletions
+13
View File
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Actions;
enum Action: string
{
case PLUS = 'plus';
case MINUS = 'minus';
case MULTIPLY = 'multiply';
case DIVISION = 'division';
}
+43
View File
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Csv;
use Generator;
use RuntimeException;
class CsvReader
{
public function __construct(
private readonly string $filePath
) {
if (!file_exists($this->filePath)) {
$handle = fopen($this->filePath, 'wb');
fclose($handle);
}
}
/**
* @return Generator<CsvRow>
*/
public function readData(): Generator
{
if (($handle = fopen($this->filePath, 'rb')) !== false) {
while (($row = fgetcsv($handle, null, ';')) !== false) {
if (!$row) {
throw new RuntimeException('Cannot read lines form CSV.');
}
if (!isset($row[0], $row[1])) {
continue;
}
$csvRow = new CsvRow(
(int)filter_var($row[0], FILTER_SANITIZE_NUMBER_INT),
(int)filter_var($row[1], FILTER_SANITIZE_NUMBER_INT)
);
yield $csvRow;
}
fclose($handle);
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Csv;
final class CsvRow
{
public function __construct(
public readonly int $number1,
public readonly int $number2
) {
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace App\Csv;
use RuntimeException;
class CsvWriter
{
private string $separator = ';';
public function __construct(private readonly string $filePath)
{
file_put_contents($this->filePath, '');
}
public function addLine(array $data): void
{
$handle = fopen($this->filePath, 'ab');
if (!$handle) {
throw new RuntimeException('Could not open file.');
}
fputcsv($handle, $data, $this->separator);
fclose($handle);
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App;
use App\Csv\CsvReader;
use App\Csv\CsvWriter;
use App\Logger\LoggerInterface;
use App\Operations\Exceptions\InvalidResultException;
use App\Operations\MathOperationInterface;
class CsvCalculationService
{
public function __construct(
private readonly CsvReader $csvReader,
private readonly CsvWriter $csvWriter,
private readonly MathOperationInterface $operation,
private readonly LoggerInterface $logger
) {
}
public function execute(): void
{
$this->logger->logMessage(sprintf('Start %s operation.%s', $this->operation->getDescription(), PHP_EOL));
foreach ($this->csvReader->readData() as $data) {
try {
$result = $this->operation->calculate($data->number1, $data->number2);
$this->csvWriter->addLine(
[$data->number1, $data->number2, $result]
);
} catch (InvalidResultException $e) {
$this->logger->logMessage(
sprintf(
'Numbers %d and %d are wrong. %s%s',
$data->number1,
$data->number2,
$e->getMessage(),
PHP_EOL
)
);
}
}
$this->logger->logMessage(sprintf('Finished %s operation.%s', $this->operation->getDescription(), PHP_EOL));
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Logger;
class FileLogger implements LoggerInterface
{
private const LOG_FILE = __DIR__ . '/../../log.txt';
public function __construct()
{
file_put_contents(self::LOG_FILE, '');
}
public function logMessage(string $message): void
{
$logMessage = sprintf('[%s] %s', date(DATE_ATOM), $message);
file_put_contents(self::LOG_FILE, $logMessage, FILE_APPEND);
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace App\Logger;
interface LoggerInterface
{
public function logMessage(string $message): void;
}
+26
View File
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App;
use App\Actions\Action;
use App\Operations\Add;
use App\Operations\Divide;
use App\Operations\MathOperationInterface;
use App\Operations\Subtract;
use App\Operations\Multiply;
use App\Options\CliOption;
class MathOperationFactory
{
public static function getOperationFromCliOption(CliOption $cliOption): MathOperationInterface
{
return match ($cliOption->action) {
Action::PLUS => new Add(),
Action::MINUS => new Subtract(),
Action::MULTIPLY => new Multiply(),
Action::DIVISION => new Divide(),
};
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Operations;
use App\Actions\Action;
use App\Operations\Exceptions\InvalidResultException;
final class Add implements MathOperationInterface
{
public function calculate(int $number1, int $number2): string
{
$result = $number1 + $number2;
if ($result < 0) {
throw new InvalidResultException();
}
return (string)$result;
}
public function getDescription(): string
{
return Action::PLUS->value;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Operations;
use App\Actions\Action;
use App\Operations\Exceptions\InvalidResultException;
final class Divide implements MathOperationInterface
{
public function calculate(int $number1, int $number2): string
{
if ($number2 === 0) {
throw new InvalidResultException('Division by 0 not possible.');
}
$result = $number1 / $number2;
if ($result < 0) {
throw new InvalidResultException();
}
return (string)($number1 / $number2);
}
public function getDescription(): string
{
return Action::DIVISION->value;
}
}
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace App\Operations\Exceptions;
use Exception;
use Throwable;
final class InvalidResultException extends Exception
{
public function __construct(string $message = 'Result smaller than 0.', int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Operations;
use App\Operations\Exceptions\InvalidResultException;
interface MathOperationInterface
{
/**
* @throws InvalidResultException
*/
public function calculate(int $number1, int $number2): string;
public function getDescription(): string;
}
+27
View File
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Operations;
use App\Actions\Action;
use App\Operations\Exceptions\InvalidResultException;
final class Multiply implements MathOperationInterface
{
public function calculate(int $number1, int $number2): string
{
$result = $number1 * $number2;
if ($result < 0) {
throw new InvalidResultException();
}
return (string)$result;
}
public function getDescription(): string
{
return Action::MULTIPLY->value;
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Operations;
use App\Actions\Action;
use App\Operations\Exceptions\InvalidResultException;
final class Subtract implements MathOperationInterface
{
public function calculate(int $number1, int $number2): string
{
$result = $number1 - $number2;
if ($result < 0) {
throw new InvalidResultException();
}
return (string)$result;
}
public function getDescription(): string
{
return Action::MINUS->value;
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\Options;
use App\Actions\Action;
use RuntimeException;
final class CliOption
{
private function __construct(
public Action $action,
public string $file
) {
}
public static function fromArray(array $options): self
{
$action = $options['a'] ?? $options['action'];
$file = $options['f'] ?? $options['file'];
if (!$action) {
throw new RuntimeException('Missing Parameter -a <action>');
}
if (!$file) {
throw new RuntimeException('Missing Parameter -f <file>');
}
if (!file_exists($file)) {
throw new RuntimeException("File '$file' not found.");
}
$action = Action::tryFrom($action) ?? throw new RuntimeException("Invalid action '$action'");
return new self(
$action,
$file
);
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace App\Options;
enum Long: string
{
case ACTION = 'action:';
case FILE = 'file:';
public static function asArray(): array
{
return array_column(self::cases(), 'value');
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace App\Options;
enum Short: string
{
case ACTION = 'a';
case FILE = 'f';
public static function asString(): string
{
return implode(':', array_column(self::cases(), 'value')) . ':';
}
}