diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5595fd6..dfd32a0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,6 +42,33 @@ jobs: - name: Run tests run: vendor/bin/phpunit + coverage: + name: Coverage + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: pcov + + - name: Install dependencies + uses: ramsey/composer-install@4.0.0 + + - name: Run tests with coverage + run: vendor/bin/phpunit --coverage-clover=coverage.xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false + static-analysis: name: Static Analysis runs-on: ubuntu-latest diff --git a/README.md b/README.md index e236223..c2a03fa 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # PHP Error Boundary +[![Tests](https://github.com/CleatSquad/php-error-boundary/actions/workflows/tests.yml/badge.svg)](https://github.com/CleatSquad/php-error-boundary/actions/workflows/tests.yml) +[![codecov](https://codecov.io/gh/CleatSquad/php-error-boundary/branch/main/graph/badge.svg)](https://codecov.io/gh/CleatSquad/php-error-boundary) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![PHP Version](https://img.shields.io/badge/php-%3E%3D8.2-777bb4.svg)](composer.json) @@ -89,6 +91,19 @@ ErrorBoundary::setShutdownInterception(function (array $error): bool { }); ``` +## Examples + +Full, runnable-style snippets for common setups live in +[`examples/`](examples/): + +- [`examples/basic.php`](examples/basic.php) — installing at the entry + point of a plain PHP script. +- [`examples/psr3-logger.php`](examples/psr3-logger.php) — wiring a PSR-3 + logger (Monolog, or any other implementation). +- [`examples/slim.php`](examples/slim.php) — a Slim 4 application. +- [`examples/sse-interception.php`](examples/sse-interception.php) — + intercepting a fatal mid-stream on an active Server-Sent Events response. + ## Design notes **No stack trace, no file path, ever reaches the client.** The default diff --git a/examples/basic.php b/examples/basic.php new file mode 100644 index 0000000..9715c10 --- /dev/null +++ b/examples/basic.php @@ -0,0 +1,31 @@ + $user->getName()]; +} + +echo json_encode(handleRequest()); diff --git a/examples/psr3-logger.php b/examples/psr3-logger.php new file mode 100644 index 0000000..2b76c82 --- /dev/null +++ b/examples/psr3-logger.php @@ -0,0 +1,38 @@ +pushHandler(new StreamHandler('php://stdout')); + +ErrorBoundary::install(null, $logger); + +header('Content-Type: application/json'); + +function handleRequest(): array +{ + throw new RuntimeException('Upstream payment provider is unreachable.'); +} + +echo json_encode(handleRequest()); + +// stdout now carries a structured log line for this exception, in addition +// to the JSON envelope the client received — one event, two destinations. diff --git a/examples/slim.php b/examples/slim.php new file mode 100644 index 0000000..6a1f106 --- /dev/null +++ b/examples/slim.php @@ -0,0 +1,41 @@ +get('/status', function ($request, $response) { + $response->getBody()->write(json_encode(['status' => 'ok'])); + + return $response->withHeader('Content-Type', 'application/json'); +}); + +$app->get('/crash', function () { + // A memory_limit exhaustion here (not simulated — genuinely uncatchable) + // is exactly the case Slim's own error middleware cannot see coming: + // it is a PHP fatal, not a Throwable. ErrorBoundary's shutdown handler + // answers with the same JSON envelope regardless. + throw new RuntimeException('Unreachable in this example — illustrates where the boundary sits.'); +}); + +$app->run(); diff --git a/examples/sse-interception.php b/examples/sse-interception.php new file mode 100644 index 0000000..2083470 --- /dev/null +++ b/examples/sse-interception.php @@ -0,0 +1,46 @@ + 'The stream ended unexpectedly.']) . "\n\n"; + + return true; // Skip the boundary's standard JSON emit — already answered. +}); + +function streamEvents(): void +{ + echo "event: message\n"; + echo 'data: ' . json_encode(['step' => 1]) . "\n\n"; + flush(); + + // Simulate a bug partway through the stream. + $state = null; + echo 'data: ' . json_encode(['step' => $state->next()]) . "\n\n"; +} + +streamEvents();