Console Tool Refactoring
This commit is contained in:
@@ -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';
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Logger;
|
||||
|
||||
interface LoggerInterface
|
||||
{
|
||||
public function logMessage(string $message): void;
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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')) . ':';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user