Skip to content

Commit e3a1034

Browse files
authored
Merge cc19a6a into c8cf78f
2 parents c8cf78f + cc19a6a commit e3a1034

File tree

6 files changed

+231
-1
lines changed

6 files changed

+231
-1
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"nexusphp/clock": "self.version",
3535
"nexusphp/collection": "self.version",
3636
"nexusphp/option": "self.version",
37-
"nexusphp/phpstan-nexus": "self.version"
37+
"nexusphp/phpstan-nexus": "self.version",
38+
"nexusphp/suppression": "self.version"
3839
},
3940
"provide": {
4041
"psr/clock-implementation": "1.0"

src/Nexus/Suppression/LICENSE

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

src/Nexus/Suppression/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Nexus Suppression
2+
3+
Provides abstractions around error management, steering usages away from the error
4+
suppression operator (`@`). This way we make the intent clearer (e.g., suppressing
5+
errors vs handling them explicitly).
6+
7+
## Installation
8+
9+
composer require nexusphp/suppression
10+
11+
## Getting Started
12+
13+
Instead of using the error suppression operator, which is considered bad practice,
14+
you can either wrap the expression in a closure then pass it to either `Silencer::box()`
15+
or `Silencer::suppress()`.
16+
17+
### `Silencer::box()`
18+
19+
You will typically use the `box()` method of `Silencer` when you want to execute an
20+
error-prone operation, get its result, and get also its error message.
21+
22+
```php
23+
// instead of:
24+
$result = @mkdir('tests/Suppression');
25+
26+
// use:
27+
[$result, $message] = (new Silencer())->box(fn(): bool => mkdir('tests/Suppression'));
28+
29+
var_dump($result); // bool(false)
30+
var_dump($message); // string(11) "File exists"
31+
32+
```
33+
34+
The `box` method accepts a `Closure` that returns the result of the operation. The method then
35+
return this result plus the error message, if any. If no error occurred, error message is `null`.
36+
37+
### `Silencer::suppress()`
38+
39+
You may use the `suppress()` method when you don't want the error message but just want to
40+
retrieve the operation's result without the error, if any. The syntax is similar to `box()`:
41+
just pass a `Closure` returning the result of the operation.
42+
43+
## License
44+
45+
Nexus Suppression is licensed under the [MIT License][1].
46+
47+
## Resources
48+
49+
* [Report issues][2] and [send pull requests][3] in the [main Nexus repository][4]
50+
51+
[1]: LICENSE
52+
[2]: https://github.com/NexusPHP/framework/issues
53+
[3]: https://github.com/NexusPHP/framework/pulls
54+
[4]: https://github.com/NexusPHP/framework

src/Nexus/Suppression/Silencer.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of the Nexus framework.
7+
*
8+
* (c) John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace Nexus\Suppression;
15+
16+
final class Silencer
17+
{
18+
/**
19+
* @template T
20+
*
21+
* @param (\Closure(): T) $func
22+
*
23+
* @return array{T, null|string}
24+
*/
25+
public function box(\Closure $func): array
26+
{
27+
$message = null;
28+
29+
set_error_handler(static function (int $errno, string $errstr) use (&$message): bool {
30+
$message = $errstr;
31+
32+
if (str_contains($message, '): ')) {
33+
$message = substr($message, (int) strpos($message, '): ') + 3);
34+
}
35+
36+
return true;
37+
});
38+
39+
try {
40+
return [$func(), $message];
41+
} finally {
42+
restore_error_handler();
43+
}
44+
}
45+
46+
/**
47+
* @template T
48+
*
49+
* @param (\Closure(): T) $func
50+
*
51+
* @return T
52+
*/
53+
public function suppress(\Closure $func): mixed
54+
{
55+
$prevErrorLevel = error_reporting(0);
56+
57+
try {
58+
return $func();
59+
} finally {
60+
error_reporting($prevErrorLevel);
61+
}
62+
}
63+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "nexusphp/suppression",
3+
"description": "Provides abstractions around error management.",
4+
"license": "MIT",
5+
"type": "library",
6+
"keywords": [
7+
"nexus",
8+
"suppression"
9+
],
10+
"authors": [
11+
{
12+
"name": "John Paul E. Balandan, CPA",
13+
"email": "paulbalandan@gmail.com"
14+
}
15+
],
16+
"support": {
17+
"issues": "https://github.com/NexusPHP/framework/issues",
18+
"source": "https://github.com/NexusPHP/framework"
19+
},
20+
"require": {
21+
"php": "^8.3"
22+
},
23+
"minimum-stability": "dev",
24+
"prefer-stable": true,
25+
"autoload": {
26+
"psr-4": {
27+
"Nexus\\Suppression\\": ""
28+
}
29+
},
30+
"config": {
31+
"optimize-autoloader": true,
32+
"preferred-install": "dist",
33+
"sort-packages": true
34+
}
35+
}

tests/Suppression/SilencerTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of the Nexus framework.
7+
*
8+
* (c) John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace Nexus\Tests\Suppression;
15+
16+
use Nexus\Suppression\Silencer;
17+
use PHPUnit\Framework\Attributes\CoversClass;
18+
use PHPUnit\Framework\Attributes\Group;
19+
use PHPUnit\Framework\TestCase;
20+
21+
/**
22+
* @internal
23+
*/
24+
#[CoversClass(Silencer::class)]
25+
#[Group('unit-test')]
26+
final class SilencerTest extends TestCase
27+
{
28+
public function testSilencerBox(): void
29+
{
30+
[$result, $message] = (new Silencer())->box(
31+
static fn(): false|string => file_get_contents('non-existent-file.txt'),
32+
);
33+
self::assertFalse($result);
34+
self::assertSame('Failed to open stream: No such file or directory', $message);
35+
36+
[$result, $message] = (new Silencer())->box(
37+
static fn(): false|string => file_get_contents(__FILE__),
38+
);
39+
self::assertIsString($result);
40+
self::assertNull($message);
41+
}
42+
43+
public function testSilencerSuppress(): void
44+
{
45+
$prevErrorLevel = error_reporting();
46+
47+
$result = (new Silencer())->suppress(static function (): int {
48+
trigger_error('Test', E_USER_WARNING);
49+
50+
return 30;
51+
});
52+
self::assertSame(30, $result);
53+
54+
self::assertSame($prevErrorLevel, error_reporting());
55+
}
56+
}

0 commit comments

Comments
 (0)