Skip to content

Commit

Permalink
Merge pull request #2 from WonderNetwork/feature/fingers-crossed-handler
Browse files Browse the repository at this point in the history
`FindersCrossedHandler`
  • Loading branch information
mlebkowski authored Nov 6, 2024
2 parents 2e4df3b + 241bb3b commit a05736d
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 0 deletions.
27 changes: 27 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,30 @@ it throws if it encounters some unexpected input.
* `allString()`, `allInt()`, `allBool()`
* ensures all items of payload match a given type
* returns an array of scalars of that type (`string[]`, `int[]`, `bool[]`)

## Fingers Crossed CLI handler

For all those pesky cron jobs, where on one hand you’d like to silence the output
because it causes noisy emails for no reason, but at the same time you don’t want
to lose context when something bad happens. Wrap your commands in a
`FingersCrossedHandler` like so:

```php
public function execute(InputInterface $input, OutputInterface $output): int {
return FingersCrossedHandler::of($input, $output)->run(
function (InputParams $input, FingersCrossedOutput $output) {
$output->write("Hello world!")
return 1;
},
);
}
```

The output will be silenced except for the following situatios:

* You increase the output verbosity using the `-v` flag or the `setVerbosity()` method
* The command returns a non-zero exit code
* The command throws

In the former case, the output will be written in realtime, and in the two
latter ones you can expect it writtein in bulk at the end.
42 changes: 42 additions & 0 deletions src/Cli/FingersCrossedHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);

namespace WonderNetwork\SlimKernel\Cli;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;

final class FingersCrossedHandler {
private InputInterface $input;
private OutputInterface $output;

public static function of(InputInterface $input, OutputInterface $output): self {
return new self($input, $output);
}

private function __construct(InputInterface $input, OutputInterface $output) {
$this->input = $input;
$this->output = $output;
}

/**
* @param callable(InputParams $input, FingersCrossedOutput $output):int $closure
* @return int
* @throws Throwable
*/
public function run(callable $closure): int {
$output = new FingersCrossedOutput($this->output);
try {
$result = $closure(InputParams::ofInput($this->input), $output);
if (Command::SUCCESS !== $result) {
$output->flush();
}
} catch (Throwable $e) {
$output->flush();
throw $e;
}
return $result;
}
}
33 changes: 33 additions & 0 deletions src/Cli/FingersCrossedOutput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);

namespace WonderNetwork\SlimKernel\Cli;

use Symfony\Component\Console\Output\OutputInterface;

final class FingersCrossedOutput {
private OutputInterface $output;
/** @var string[] */
private array $messages = [];

public function __construct(OutputInterface $output) {
$this->output = $output;
}

public function writeln(string $message): void {
if ($this->isBuffering()) {
$this->messages[] = $message;
return;
}
$this->flush();
$this->output->writeln($message);
}

public function flush(): void {
$this->output->writeln(array_slice($this->messages, 0));
}

public function isBuffering(): bool {
return false === $this->output->isVerbose();
}
}
92 changes: 92 additions & 0 deletions tests/Cli/FingersCrossedHandlerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);

namespace WonderNetwork\SlimKernel\Cli;

use PHPUnit\Framework\TestCase;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;

class FingersCrossedHandlerTest extends TestCase {
private BufferedOutput $spy;
private FingersCrossedHandler $sut;

protected function setUp(): void {
$this->spy = new BufferedOutput();
$this->sut = FingersCrossedHandler::of(new ArrayInput([]), $this->spy);
}

public function testHidesNormalOutputOnSuccess(): void {
$result = $this->sut->run(
function (InputParams $input, FingersCrossedOutput $output) {
$output->writeln("Hello world");
return Command::SUCCESS;
},
);
self::assertSame(0, $result);
self::assertSame("", $this->spy->fetch());
}

public function testDisplaysAllOutputOnFailure(): void {
$result = $this->sut->run(
function (InputParams $input, FingersCrossedOutput $output) {
$output->writeln("Hello world");
return Command::FAILURE;
},
);
self::assertSame(1, $result);
self::assertSame("Hello world\n", $this->spy->fetch());
}

public function testCorrectlyFormatsAllOutput(): void {
$this->sut->run(
function (InputParams $input, FingersCrossedOutput $output) {
$output->writeln("Alpha");
$output->writeln("Bravo");
return Command::FAILURE;
},
);
self::assertSame("Alpha\nBravo\n", $this->spy->fetch());
}

public function testDisplaysAllOutputWhenThrows(): void {
$e = null;
try {
$this->sut->run(
function (InputParams $input, FingersCrossedOutput $output) {
$output->writeln("Hello world");
throw new RuntimeException();
},
);
} catch (Throwable $e) {}
self::assertSame("Hello world\n", $this->spy->fetch());
self::assertInstanceOf(Throwable::class, $e);
}

public function testDisplaysAllOutputWhenVerbose(): void {
$this->spy->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
$this->sut->run(
function (InputParams $input, FingersCrossedOutput $output) {
$output->writeln("Hello world");
return Command::SUCCESS;
},
);
self::assertSame("Hello world\n", $this->spy->fetch());
}

public function testOutputIsInOrderWhenVerbositiIsIncreased(): void {
$this->sut->run(
function (InputParams $input, FingersCrossedOutput $output) {
$output->writeln("Alpha");
$this->spy->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
$output->writeln("Bravo");
return Command::SUCCESS;
},
);
self::assertSame("Alpha\nBravo\n", $this->spy->fetch());
}
}

0 comments on commit a05736d

Please sign in to comment.