Skip to content

Commit 214d605

Browse files
committed
Feat: implement exception unwrapper
1 parent 6fd9a52 commit 214d605

23 files changed

+780
-1
lines changed

.gitattributes

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/.github export-ignore
2+
/.gitattributes export-ignore
3+
/.gitignore export-ignore
4+
/tests export-ignore
5+
/ecs.php export-ignore
6+
/phpstan.dist.neon export-ignore
7+
/phpunit.xml.dist export-ignore
8+
/rector.php export-ignore

.github/workflows/ci.yaml

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
name: 'Continuous Integration'
2+
3+
on:
4+
pull_request: ~
5+
workflow_dispatch: ~
6+
push:
7+
branches: [ main ]
8+
9+
jobs:
10+
composer-validate:
11+
name: Composer Validate
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: 'Checkout Code'
15+
uses: actions/checkout@v4
16+
17+
- name: 'Validate composer.json'
18+
run: composer validate --strict --ansi
19+
20+
code-style:
21+
needs: composer-validate
22+
name: ${{ matrix.actions.name }}
23+
runs-on: ubuntu-latest
24+
strategy:
25+
fail-fast: false
26+
matrix:
27+
actions:
28+
- name: 'Coding Standard'
29+
run: vendor/bin/ecs check --no-progress-bar --ansi
30+
- name: 'Rector'
31+
run: vendor/bin/rector process --dry-run --no-progress-bar --ansi
32+
steps:
33+
- name: 'Checkout Code'
34+
uses: actions/checkout@v4
35+
36+
- name: 'Setup PHP'
37+
uses: shivammathur/setup-php@v2
38+
with:
39+
php-version: 8.3
40+
coverage: none
41+
tools: composer:v2
42+
43+
- name: 'Install Dependencies'
44+
uses: "ramsey/composer-install@v2"
45+
with:
46+
composer-options: "--optimize-autoloader"
47+
48+
- run: ${{ matrix.actions.run }}
49+
static-analysis:
50+
needs: composer-validate
51+
runs-on: ubuntu-latest
52+
name: ${{ matrix.actions.name }} (${{ matrix.php }}, ${{ matrix.dependencies }})
53+
strategy:
54+
fail-fast: false
55+
matrix:
56+
php: [ '8.1', '8.3' ]
57+
dependencies: [ highest, lowest ]
58+
actions:
59+
- name: 'PHPStan'
60+
run: vendor/bin/phpstan analyze --no-progress --error-format=github --ansi --configuration=phpstan.dist.neon
61+
steps:
62+
- name: 'Checkout Code'
63+
uses: actions/checkout@v4
64+
65+
- name: 'Setup PHP'
66+
uses: shivammathur/setup-php@v2
67+
with:
68+
php-version: ${{ matrix.php }}
69+
coverage: none
70+
tools: composer:v2
71+
72+
- name: 'Install Dependencies'
73+
uses: "ramsey/composer-install@v2"
74+
with:
75+
composer-options: "--optimize-autoloader"
76+
dependency-versions: ${{ matrix.dependencies }}
77+
78+
- run: ${{ matrix.actions.run }}
79+
test:
80+
needs: composer-validate
81+
name: PHPUnit
82+
runs-on: ubuntu-latest
83+
strategy:
84+
matrix:
85+
php: [ '8.1', '8.2' ]
86+
dependencies: [ highest, lowest ]
87+
steps:
88+
- name: 'Checkout Code'
89+
uses: actions/checkout@v4
90+
91+
- name: 'Setup PHP'
92+
uses: shivammathur/setup-php@v2
93+
with:
94+
php-version: ${{ matrix.php }}
95+
coverage: none
96+
tools: composer:v2
97+
98+
- name: 'Install Dependencies'
99+
uses: "ramsey/composer-install@v2"
100+
with:
101+
composer-options: "--optimize-autoloader"
102+
dependency-versions: ${{ matrix.dependencies }}
103+
104+
- name: 'Run PHPUnit'
105+
run: vendor/bin/phpunit --testdox --colors=always --configuration=phpunit.xml.dist
106+
107+
code-coverage:
108+
needs: [ test, static-analysis ]
109+
name: PHPUnit Coverage
110+
runs-on: ubuntu-latest
111+
steps:
112+
- name: 'Checkout Code'
113+
uses: actions/checkout@v4
114+
115+
- name: 'Setup PHP'
116+
uses: shivammathur/setup-php@v2
117+
with:
118+
php-version: 8.3
119+
coverage: xdebug
120+
tools: composer:v2
121+
122+
- name: 'Install Dependencies'
123+
uses: "ramsey/composer-install@v2"
124+
with:
125+
composer-options: "--optimize-autoloader"
126+
127+
- name: 'Run PHPUnit with Coverage'
128+
run: vendor/bin/phpunit --configuration=phpunit.xml.dist --coverage-clover=coverage.xml
129+
130+
- name: 'Upload coverage reports to Codecov'
131+
uses: codecov/codecov-action@v4
132+
with:
133+
fail_ci_if_error: true
134+
files: ./coverage.xml
135+
token: ${{ secrets.CODECOV_TOKEN }}

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
vendor/
2+
.phpunit.cache/
3+
.phpunit.result.cache
4+
composer.lock
5+
coverage.xml

LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) Yevhen Sidelnyk
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
this software and associated documentation files (the "Software"), to deal in
5+
the Software without restriction, including without limitation the rights to
6+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7+
of the Software, and to permit persons to whom the Software is furnished to do
8+
so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.

README.md

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,77 @@
1-
# exception-toolkit-bundle
1+
# Exception Toolkit
2+
3+
🧰 Provides a set of tools to handle exceptions in PHP applications.
4+
5+
[![Build Status](https://img.shields.io/github/actions/workflow/status/phphd/exception-toolkit/ci.yaml?branch=main)](https://github.com/phphd/exception-toolkit/actions?query=branch%3Amain)
6+
[![Codecov](https://codecov.io/gh/phphd/exception-toolkit/graph/badge.svg?token=GZRXWYT55Z)](https://codecov.io/gh/phphd/exception-toolkit)
7+
[![Packagist Downloads](https://img.shields.io/packagist/dt/phphd/exception-toolkit.svg)](https://packagist.org/packages/phphd/exception-toolkit)
8+
[![Licence](https://img.shields.io/github/license/phphd/exception-toolkit.svg)](https://github.com/phphd/exception-toolkit/blob/main/LICENSE)
9+
10+
## Installation 📥
11+
12+
1. Install via composer
13+
14+
```sh
15+
composer require phphd/exception-toolkit
16+
```
17+
18+
2. In case you are using symfony, enable the bundle in the `bundles.php`
19+
20+
```php
21+
PhPhD\ExceptionToolkit\Bundle\PhdExceptionToolkitBundle::class => ['all' => true],
22+
```
23+
24+
## Provided tools ⚙️
25+
26+
### Exception Unwrapper
27+
28+
Allows you to unwrap composite exceptions and get the atomic errors you are interested in:
29+
30+
```php
31+
use PhPhD\ExceptionToolkit\Unwrapper\ExceptionUnwrapper;
32+
33+
/** @var ExceptionUnwrapper $unwrapper */
34+
$unwrapper = getUnwrapper();
35+
36+
$compositeException = new CompositeException([
37+
new InvalidEmailException(),
38+
new CompositeException([
39+
new InvalidPasswordException(),
40+
]),
41+
]);
42+
43+
[$emailError, $passwordError] = $unwrapper->unwrap($compositeException);
44+
```
45+
46+
In this example, errors were retrieved from composite exceptions: `$emailError` will be an
47+
instance of `InvalidEmailException` and `$passwordError` will be an
48+
instance of `InvalidPasswordException` that were wrapped in the composite exception.
49+
50+
#### Symfony integration
51+
52+
In symfony application you could use ExceptionUnwrapper service:
53+
54+
```php
55+
public function __construct(
56+
#[Autowire('@phd_exception_toolkit.exception_unwrapper')]
57+
private ExceptionUnwrapper $exceptionUnwrapper,
58+
) {}
59+
```
60+
61+
This will provide you with full stack of defined unwrappers bundled into a single instance.
62+
63+
> If you want to define custom unwrapper,
64+
> you should decorate `phd_exception_toolkit.exception_unwrapper.stack`
65+
> service.
66+
67+
#### Built-in unwrappers
68+
69+
##### Messenger
70+
71+
If you are using symfony messenger, `Symfony\Component\Messenger\Exception\WrappedExceptionsInterface`
72+
will be unwrapped automatically.
73+
74+
##### Amp
75+
76+
If you are using Amp, `Amp\CompositeException` will be unwrapped automatically.
77+

composer.json

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
{
2+
"name": "phphd/exception-toolkit",
3+
"description": "Exception processing toolkit",
4+
"type": "symfony-bundle",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Yevhen Sidelnyk",
9+
"email": "[email protected]"
10+
}
11+
],
12+
"minimum-stability": "stable",
13+
"require": {
14+
"php": ">=8.1"
15+
},
16+
"require-dev": {
17+
"symfony/http-kernel": "^6.0 | ^7.0",
18+
"symfony/dependency-injection": "^6.2 | ^7.0",
19+
"symfony/config": "^6.0 | ^7.0",
20+
"symfony/messenger": "^6.4 | ^7.0",
21+
"amphp/amp": "^3.0",
22+
"symfony/var-dumper": "^6.0 | ^7.0",
23+
"tomasvotruba/type-coverage": "^0.3.1",
24+
"phpstan/phpstan": "^1.11",
25+
"phpunit/phpunit": "^10.5",
26+
"phpstan/phpstan-phpunit": "^1.4",
27+
"phphd/coding-standard": "~0.5.3",
28+
"nyholm/symfony-bundle-test": "^3.0"
29+
},
30+
"conflict": {
31+
"symfony/http-kernel": "<6.0 || >=8.0",
32+
"symfony/dependency-injection": "<6.2 || >=8.0" ,
33+
"symfony/config": "<6.0 || >=8.0",
34+
"symfony/messenger": ">=8.0",
35+
"amphp/amp": ">=4.0"
36+
},
37+
"suggest": {
38+
"symfony/messenger": "There's an unwrapper for Messenger exceptions of ^6.4 or above",
39+
"amphp/amp": "There's an unwrapper for Amp exceptions of ^3.0 or above"
40+
},
41+
"repositories": [
42+
{
43+
"type": "vcs",
44+
"url": "https://github.com/phphd/coding-standard"
45+
}
46+
],
47+
"autoload": {
48+
"psr-4": {
49+
"PhPhD\\ExceptionToolkit\\": "src/"
50+
}
51+
},
52+
"autoload-dev": {
53+
"psr-4": {
54+
"PhPhD\\ExceptionToolkit\\Tests\\": "tests/"
55+
}
56+
},
57+
"scripts": {
58+
"ci:pack": [
59+
"@ci:ecs",
60+
"@ci:rector",
61+
"@ci:phpstan",
62+
"@ci:unit-test",
63+
"@ci:integration-test"
64+
],
65+
"ci:ecs": "vendor/bin/ecs check",
66+
"ci:ecs-fix": "vendor/bin/ecs check --fix",
67+
"ci:rector": "vendor/bin/rector process --dry-run -vv",
68+
"ci:rector-fix": "vendor/bin/rector process",
69+
"ci:phpstan": "vendor/bin/phpstan analyze",
70+
"ci:test": "vendor/bin/phpunit --testdox --colors=always",
71+
"ci:unit-test": "vendor/bin/phpunit --testdox --colors=always --testsuite=Unit",
72+
"ci:integration-test": "vendor/bin/phpunit --testdox --colors=always --testsuite=Integration"
73+
}
74+
}

ecs.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhPhD\CodingStandard\ValueObject\Set\PhdSetList;
6+
use Symplify\EasyCodingStandard\Config\ECSConfig;
7+
8+
return static function (ECSConfig $ecsConfig): void {
9+
$ecsConfig->sets([PhdSetList::ecs()->getPath()]);
10+
11+
$ecsConfig->paths([__DIR__.'/src', __DIR__.'/tests']);
12+
$ecsConfig->skip([__DIR__.'/vendor']);
13+
};

phpstan.dist.neon

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
includes:
2+
- vendor/phpstan/phpstan-phpunit/extension.neon
3+
- vendor/phpstan/phpstan-phpunit/rules.neon
4+
- vendor/tomasvotruba/type-coverage/config/extension.neon
5+
6+
parameters:
7+
level: 9
8+
phpVersion: 80127
9+
editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%'
10+
paths:
11+
- src
12+
- tests
13+
fileExtensions:
14+
- 'php'
15+
type_coverage:
16+
declare: 100
17+
return_type: 100
18+
param_type: 100
19+
property_type: 100
20+
constant: 0 # requires php 8.3

phpunit.xml.dist

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
cacheDirectory=".phpunit.cache"
6+
executionOrder="depends,defects"
7+
requireCoverageMetadata="true"
8+
beStrictAboutOutputDuringTests="true"
9+
failOnRisky="true"
10+
failOnWarning="true">
11+
<testsuites>
12+
<testsuite name="Unit">
13+
<directory suffix="UnitTest.php">tests</directory>
14+
</testsuite>
15+
<testsuite name="Integration">
16+
<directory suffix="IntegrationTest.php">tests</directory>
17+
</testsuite>
18+
</testsuites>
19+
20+
<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
21+
<include>
22+
<directory>src</directory>
23+
</include>
24+
</source>
25+
</phpunit>

0 commit comments

Comments
 (0)