Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .agents/skills/coisa-error-handler/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
name: coisa-error-handler
description: Integrate CoiSA Error Handler (coisa/error-handler) into PHP applications using explicit process handlers, FastForward service providers or PSR-15 middleware, and verify reporting and callback restoration.
license: MIT
---

# CoiSA Error Handler integration

Package: `coisa/error-handler`. Source and maintenance:
[coisa/error-handler on GitHub](https://github.com/coisa/error-handler).
This skill belongs to that library and retains its package identity when copied
into a consuming project.

Use the installed package or selected checkout as the API source. This skill
describes the PHP 8.3+ development line after 2.0; verify the version before
using its provider or changed exit-status contract. A future php-fast-forward
move is not an existing package name or namespace alias.

## Choose the scope

For a CLI or process bootstrap, create one handler with an application-owned
reporter and keep it registered until process exit when it must see uncaught
exceptions. For a callable operation whose caller catches failures, unregister
in `finally`. PHP runs that `finally` before dispatching an uncaught exception.

```php
use CoiSA\ErrorHandler\ErrorHandler;
use CoiSA\ErrorHandler\Handler\CallableThrowableHandler;

$errors = new ErrorHandler(new CallableThrowableHandler(
static function (Throwable $error): void {
error_log($error->getMessage()); // Replace with an appropriate private sink.
}
));
$errors->register();
```

Load Composer's autoloader first. Direct `handleThrowable()` calls return;
the registered uncaught-exception callback exits 1 after successful reporting.
Recoverable warnings are reported and execution continues. The default error
conversion honors suppression and `error_reporting()`.

## Wire FastForward definitions

Use `CoiSA\ErrorHandler\ServiceProvider\ErrorHandlerServiceProvider` with
`FastForward\Container\container()`. It implements the Interop provider contract
(`getFactories()` and `getExtensions()`), and resolves
`CoiSA\ErrorHandler\ErrorHandlerInterface`. To override the reporter binding
`CoiSA\ErrorHandler\Handler\ThrowableHandlerInterface`, merge definitions with
`FastForward\Container\ServiceProvider\AggregateServiceProvider`: library first,
application **last**, then pass that one provider into `container(...)`.

Separate variadic providers allow FastForward to fall back to the library's
silent default if the application's reporter fails with a PSR container error.
Merged definitions replace that default and make required-reporter construction
fail. FastForward may translate the PSR error to not-found; do not promise that
its original exception cause survives. Use the executable example in the
matching checkout for the complete composition.

The factory maps do not install PHP callbacks: explicitly register the resolved
process handler at bootstrap. An empty default reporter aggregate is silent;
configure a reporter or an event dispatcher before expecting diagnostics.

FastForward caches resolved instances, including middleware. To create a fresh
handler, resolve `CoiSA\ErrorHandler\Container\Factory\ErrorHandlerFactory` and
invoke it with the container. For fresh middleware, resolve and invoke
`CoiSA\ErrorHandler\ServiceProvider\Factory\ErrorHandlerMiddlewareFactory`.
Do not mistake repeated `get()` calls for a transient lifetime.

## Wire HTTP deliberately

Supply PSR-17 response and stream factories. The provider creates middleware
with a handler distinct from the process service; it restores native callbacks
after processing and after renderer failure. Create separate middleware for
nested scopes. This library does not make callbacks local to concurrent fibers
or requests, and it does not emit HTTP responses.

Reporters and renderers have different output responsibilities. Empty captured
reporter output becomes `Internal Server Error`; emitted text becomes the public
body. Keep private diagnostics out of renderer output. Renderers must use
removable, balanced buffers and must not close caller-owned buffers.

## Verify the integration

Exercise a reported warning, an uncaught throwable in a bounded child process,
and callback restoration after success and failure. If using middleware, also
exercise nesting beneath a registered process handler and renderer failure.
Check delivery counts, response redaction and exit status, not just that a
service can be resolved. Never run a fatal-error experiment in a user process.

In the library checkout, use `composer --no-plugins check` and
`php examples/fast-forward.php`. In a consuming application, add focused tests
against its configured reporters and middleware; the library's passing suite
does not prove remote logger delivery or application-specific transport behavior.

For full examples, consult the matching checkout's `docs/fast-forward.md`,
`docs/api.md` and `docs/testing.md`, or the
[source repository](https://github.com/coisa/error-handler). Keep the observed
version, integration changes, executed checks and remaining limits explicit.
10 changes: 3 additions & 7 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
.composer/
.git*
.git/
.github/
.idea/
vendor/
.php_cs
tmp/
*.cache
*.yml
phpcs.xml
phpstan.neon
Dockerfile
README.md
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: composer
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
65 changes: 65 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: CI

on:
push:
branches: [master]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: PHP ${{ matrix.php }} / ${{ matrix.dependencies }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- php: '8.3'
dependencies: lowest
- php: '8.3'
dependencies: highest
- php: '8.4'
dependencies: highest
- php: '8.5'
dependencies: locked
- php: '8.5'
dependencies: highest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: shivammathur/setup-php@b604ade2a87db23f8871b7182e69ec5e75effb45 # v2
with:
php-version: ${{ matrix.php }}
extensions: dom, mbstring, xml, xmlwriter, pdo
coverage: none
tools: composer:v2
- run: composer validate --strict --no-plugins
- if: matrix.dependencies == 'locked'
run: composer install --no-plugins --no-scripts --no-interaction --prefer-dist --no-progress
- if: matrix.dependencies == 'highest'
run: composer update --no-plugins --no-scripts --no-interaction --prefer-dist --no-progress
- if: matrix.dependencies == 'lowest'
run: composer update --prefer-lowest --prefer-stable --no-plugins --no-scripts --no-interaction --prefer-dist --no-progress
- run: composer --no-plugins check
- name: Verify the documented CLI example
run: |
python3 - <<'PY'
import subprocess
result = subprocess.run(['php', 'examples/cli.php'], capture_output=True, text=True, timeout=5)
assert result.returncode == 1 and result.stdout == ''
assert 'Example warning' in result.stderr and 'Example uncaught exception' in result.stderr
provider = subprocess.run(['php', 'examples/fast-forward.php'], capture_output=True, text=True, timeout=5)
assert provider.returncode == 0 and provider.stderr == ''
assert provider.stdout == 'Reported: FastForward provider warning\n'
PY
- name: Verify production-only autoload
run: |
composer install --no-dev --no-plugins --no-scripts --no-interaction --no-progress
php -r 'require "vendor/autoload.php"; new CoiSA\ErrorHandler\Container\ErrorHandlerContainer(); (new CoiSA\ErrorHandler\ServiceProvider\ErrorHandlerServiceProvider())->getFactories(); if (class_exists(FastForward\Container\AggregateContainer::class)) { throw new RuntimeException("FastForward must remain optional"); }'
31 changes: 31 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Documentation

on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pages
cancel-in-progress: false

jobs:
publish:
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
with:
path: docs
- uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
id: deployment
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ tests/html/
tests/clover.xml
tests/testdox.html
.*.cache
tmp/
25 changes: 3 additions & 22 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,6 @@
* @license https://opensource.org/licenses/MIT MIT License
*/

use CoiSA\PhpCsFixer\PhpCsFixer;

/**
* This file is part of coisa/logger.
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*
* @see https://github.com/coisa/factory
* @see https://12factor.net/logs
*
* @copyright Copyright (c) 2022 Felipe Sayão Lobato Abreu <github@mentor.dev.br>
* @license https://opensource.org/licenses/MIT MIT License
*/
$paths = [
__FILE__,
__DIR__,
];

$header = file_get_contents(__DIR__ . '/.docheader');

return PhpCsFixer::create($paths, $header);
return (new PhpCsFixer\Config())
->setRules(['@PSR12' => true])
->setFinder(PhpCsFixer\Finder::create()->in([__DIR__ . '/src', __DIR__ . '/tests'])->name('*.php'));
Loading
Loading