Skip to content

Commit

Permalink
Merge pull request #80 from adhocore/79-exception-handler
Browse files Browse the repository at this point in the history
  • Loading branch information
adhocore authored Mar 25, 2023
2 parents 6aa9556 + cb9e2b3 commit 2e46d00
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 9 deletions.
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ Framework agnostic Command Line Interface utilities and helpers for PHP. Build C
[![Codecov branch](https://img.shields.io/codecov/c/github/adhocore/php-cli/main.svg?style=flat-square)](https://codecov.io/gh/adhocore/php-cli)
[![StyleCI](https://styleci.io/repos/139012552/shield)](https://styleci.io/repos/139012552)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Donate 15](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+15)](https://www.paypal.me/ji10/15usd)
[![Donate 25](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+25)](https://www.paypal.me/ji10/25usd)
[![Donate 50](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+50)](https://www.paypal.me/ji10/50usd)
[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Framework+agnostic+Command+Line+Interface+utilities+and+helpers+for+PHP&url=https://github.com/adhocore/php-cli&hashtags=php,cli,cliapp,console)
[![Support](https://img.shields.io/static/v1?label=Support&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/adhocore)
<!-- [![Donate 15](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+15)](https://www.paypal.me/ji10/15usd)
[![Donate 25](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+25)](https://www.paypal.me/ji10/25usd)
[![Donate 50](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+50)](https://www.paypal.me/ji10/50usd) -->


- Command line application made easy
Expand Down Expand Up @@ -252,6 +253,25 @@ $app->add((new ConfigListCommand)->inGroup('Config'));
...
```

#### Exception handler

Set a custom exception handler as callback. The callback receives exception & exit code. The callback may rethrow exception or may exit the program or just log exception and do nothing else.

```php
$app = new Ahc\Cli\Application('App', 'v0.0.1');
$app->add(...);
$app->onException(function (Throwable $e, int $exitCode) {
// send to sentry
// write to logs

// optionally, exit with exit code:
exit($exitCode);

// or optionally rethrow, a rethrown exception is propagated to top layer caller.
throw $e;
})->handle($argv);
```

#### App help

It can be triggered manually with `$app->showHelp()` or automatic when `-h` or `--help` option is passed to `$app->parse()`.
Expand Down
22 changes: 19 additions & 3 deletions src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class Application
/** @var callable The callable to perform exit */
protected $onExit;

/** @var callable The callable to catch exception, receives exception & exit code, may rethrow exception or may exit program */
protected $onException = null;

public function __construct(protected string $name, protected string $version = '0.0.1', callable $onExit = null)
{
$this->onExit = $onExit ?? static fn (int $exitCode = 0) => exit($exitCode);
Expand Down Expand Up @@ -123,7 +126,7 @@ public function logo(string $logo = null)
}

/**
* Add a command by its name desc alias etc.
* Add a command by its name desc alias etc and return command.
*/
public function command(
string $name,
Expand All @@ -140,7 +143,7 @@ public function command(
}

/**
* Add a prepred command.
* Add a prepared command and return itself.
*/
public function add(Command $command, string $alias = '', bool $default = false): self
{
Expand Down Expand Up @@ -256,6 +259,19 @@ public function parse(array $argv): Command
return $command->parse($argv);
}

/**
* Sets exception handler callback.
*
* The callback receives exception & exit code. It may rethrow exception
* or may exit the program or just log exception and do nothing else.
*/
public function onException(callable $fn): self
{
$this->onException = $fn;

return $this;
}

/**
* Handle the request, invoke action and call exit handler.
*/
Expand All @@ -266,12 +282,12 @@ public function handle(array $argv): mixed
}

$exitCode = 255;

try {
$command = $this->parse($argv);
$result = $this->doAction($command);
$exitCode = is_int($result) ? $result : 0;
} catch (Throwable $e) {
isset($this->onException) && ($this->onException)($e, $exitCode);
$this->outputHelper()->printTrace($e);
}

Expand Down
15 changes: 12 additions & 3 deletions tests/ApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Ahc\Cli\IO\Interactor;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use Throwable;

class ApplicationTest extends TestCase
{
Expand Down Expand Up @@ -288,11 +289,19 @@ public function test_io_returns_new_instance_if_not_provided(): void
);
}

public function test_on_exception()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage($msg = 'this will be rethrown and propagated');

$cmd = (new Command('cmd'))->action(fn () => throw new InvalidArgumentException($msg));
$app = $this->newApp('test')->add($cmd)->onException(fn (Throwable $e) => throw $e);
$app->handle(['test', 'cmd']);
}

protected function newApp(string $name, string $version = '')
{
$app = new Application($name, $version ?: '0.0.1', function () {
return false;
});
$app = new Application($name, $version ?: '0.0.1', fn () => false);

return $app->io(new Interactor(static::$in, static::$ou));
}
Expand Down

0 comments on commit 2e46d00

Please sign in to comment.