Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/razshare/catpaw-unsafe
Manage errors in php.
https://github.com/razshare/catpaw-unsafe
error-handling php
Last synced: about 1 month ago
JSON representation
Manage errors in php.
- Host: GitHub
- URL: https://github.com/razshare/catpaw-unsafe
- Owner: razshare
- Created: 2024-02-13T03:00:59.000Z (11 months ago)
- Default Branch: master
- Last Pushed: 2024-02-18T21:19:42.000Z (10 months ago)
- Last Synced: 2024-06-19T18:10:14.274Z (6 months ago)
- Topics: error-handling, php
- Language: PHP
- Homepage: https://github.com/tncrazvan/catpaw-unsafe
- Size: 29.3 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
Install with
```sh
composer require catpaw/unsafe
```# Control flow is king
I am of the opinion that control flow is one of the most important things to deal with as a programmer, it affects my thinking and at times it actually guides my problem solving process.
Managing errors should not break the flow in which I control my program, I shouldn't have to jump up and down around my file to catch a new exception introduced by a new function I just invoked 20 lines above.
# Try/Catch
I found myself relying way too much on code like this
```php
try {
// some code
} catch(SomeException1 $e){
// manage error 1
} catch(SomeException2 $e) {
// manage error 2
} catch(SomeException3 $e) {
// manage error 3
}
```or even
```php
try {
// some code
} catch(SomeException1|SomeException2|SomeException3 $e){
// manage all errors in one place
}
```The last one might make sense in theory, but in practice those exceptions might each mean something different, a different cause for an error.
The reality is that very often I lump those exceptions in together because I forget to manage them or because for some reason at 4 AM I decide on the spot "yes, I should let my IDE dictate my error management".
Try/catch error handling has been (probably) the most popular way to manage errors in php, and I think it still is a valid way of dealing with errors in a global scope.
I can't argue there is something nice about having one centralized place to manage all errors, but I don't want to be forced to approach error management all the time in that manner.
If you're anything like me you might prefer managing your error inline, directly at the source, so that you deal with it when it pops up and then you don't have to think about it anymore.
# Unsafe
I have a solution.
Do not throw exceptions in your code, instead return your errors as _Unsafe_.
```php
namespace CatPaw\Unsafe;
/**
* @template T
*/
readonly class Unsafe {
/** @var T $value */
public $value;
public false|Error $error;
}
```Use the _ok()_ and _error()_ functions to create _Unsafe_ objects.
# ok()
```php
namespace CatPaw\Unsafe;
/**
* @template T
* @param T $value
* @return Unsafe
*/
function ok($value);
```
Return _ok($value)_ whenever there are no errors in your program.This function will create a new _Unsafe_ with a valid _$value_ and no error.
# error()
```php
namespace CatPaw\Core;
/**
* @param string|Error $error
* @return Unsafe
*/
function error($error);
```
Return _error($error)_ whenever you encounter an error in your program and want to propagate it upstream.This function will create a new _Unsafe_ with a _null $value_ and the given _error_.
# Example
The following example tries to read the contents of a file while managing errors.
First I'm declaring all entities involved, classes and functions.
```php
fileName, where's the file Lebowski????";
}
}/**
* Attempt to open a file.
* @param string $fileName
* @return Unsafe
*/
function openFile(string $fileName){
if(!file_exists($fileName)){
return error(new FileNotFoundError($fileName));
}
if(!$file = fopen('file.txt', 'r+')){
return error("Something went wrong while trying to open file $fileName.");
}
return ok($file);
}/**
* Attempt to read 5 bytes from the file.
* @param resource $stream
* @return Unsafe
*/
function readFile($stream){
$content = fread($stream, 5);
if(false === $content){
return error("Couldn't read from stream.");
}return ok($content);
}/**
* Attempt to close the file.
* @param resource $stream
* @return Unsafe
*/
function closeFile($stream){
if(!fclose($stream)){
return error("Couldn't close file.");
}
return ok();
}
```then
1. open a file
2. read its contents
3. close the file```php
try($error);
if ($error) {
echo $error.PHP_EOL;
die();
}// read contents
$contents = readFile($file)->try($error);
if ($error) {
echo $error.PHP_EOL;
die();
}// close file
closeFile($file)->try($error);
if ($error) {
echo $error.PHP_EOL;
die();
}echo $contents.PHP_EOL;
```
This code will print the contents of `file.txt` if all operations succeed.Each time `->try($error)` is invoked the _Unsafe_ object tries to unwrap its value.\
If the _Unsafe_ object contains an error, the value returned by `->try($error)` resolves to `null` and the variable `$error` is assigned the contained error by reference.# anyError()
You can use _anyError()_ to deal away with the repetitive snippet
```php
if($error){
echo $error.PHP_EOL;
// manage error here...
}
```Here's the same example but written using _anyError()_
```php
try($error)
or yield $error;// read contents
$contents = readFile($file)->try($error)
or yield $error;// close file
closeFile($file)->try($error)
or yield $error;return $contents;
})->try($error);if($error){
echo $error.PHP_EOL;
die();
}echo $contents.PHP_EOL;
```The _anyError()_ function takes a generator function and it consumes it step by step.
When the generator function `yield`s an _Error_ or an _Unsafe_ containing an _Error_, the _anyError_ function will stop executing the generator immediately and return a new _Unsafe_ containing the given error.
Effectively, `or yield $error` acts like
```php
if($error){
return error($error);
}
```
On the other hand, if the result of `->try()` is valid, the `or ` is not executed and the generator keeps running until it reaches the next `yield error` statement, the next `return` statement or until the generator is consumed.# Matching
Since errors are results, you can actually `match()` them
```php
$result = anyError(/* ... */)->try($error) or match($error:class){
FileNotFoundError::class => $error->getMessage(),
default => "Let me explain something to you. Um, I am not Mr. Lebowski. You're Mr. Lebowski.",};
```or apply any sort of expression that you want _inline_.