From 2af48ed195704adea70dc95ddbcced950ea92486 Mon Sep 17 00:00:00 2001 From: Bosun Egberinde Date: Wed, 27 Dec 2023 11:46:43 +0100 Subject: [PATCH] Update Readme --- README.md | 168 +++++++++++++++++++++++++++++++++++++ src/Option/None.php | 10 +++ src/Option/Option.php | 10 +++ src/Option/Some.php | 10 +++ src/helpers.php | 4 +- tests/Unit/HelpersTest.php | 6 +- 6 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a490de5 --- /dev/null +++ b/README.md @@ -0,0 +1,168 @@ +# Results + +Results is a PHP library that provides a set of helper functions and classes for handling optional values and results of operations. It is inspired by the [`Option`](https://doc.rust-lang.org/std/option/enum.Option.html) and [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) types in Rust. + +## Installation + +You can install the library via Composer: + +```bash +composer require bosunski/results +``` + +## Usage + +### Option + +The `Option` type represents an optional value: every `Option` is either `Some` and contains a value, or `None`, and does not. + +```php +use function Bosunski\Results\Option; + +$some = Option('value'); // Some +$none = Option(null); // None +``` + +### Result + +The `Result` type is a type that represents either success (`Ok`) or failure (`Err`). + +```php +use function Bosunski\Results\Result; + +$ok = Result('value'); // Ok +$err = Result(new Exception('error')); // Err +``` + +### Helper Functions + +The library provides a set of helper functions for creating `Option` and `Result` instances: + +- `Some(mixed $value): Some` +- `None(): None` +- `Option(mixed $value): Option` +- `Ok(mixed $value): Ok` +- `Err(Throwable $e): Err` +- `Result(mixed $value): Ok|Err` +- `wrap(callable $fn): Result` + +### Examples + +Optional values and results in PHP can be represented using the `Option` and `Result` types provided by the Results library. Here are some examples: + +```php +use function Bosunski\Results\Option; +use function Bosunski\Results\Result; + +// Options +$some = Option('value'); // Some +$none = Option(null); // None + +// Results +$ok = Result('value'); // Ok +$err = Result(new Exception('error')); // Err +``` + +In the above examples: + +- `Option('value')` creates an `Option` that contains a value (referred to as `Some`). +- `Option(null)` creates an `Option` that does not contain a value (referred to as `None`). +- `Result('value')` creates a `Result` that represents a successful operation (`Ok`). +- `Result(new Exception('error'))` creates a `Result` that represents a failed operation (`Err`). + + +Let's dive into more complex examples of using the `Option` and `Result` types in PHP. + +Consider a scenario where we have a function that may or may not return a value. We can use the `Option` type to handle this uncertainty. + +```php +use Bosunski\Results\Option as OptionInterface; +use function Bosunski\Results\Option; + +function findUserById($id): OptionInterface { + // Assume getUserFromDatabase is a function that returns a User object if found, null otherwise + $user = getUserFromDatabase($id); + + return Option($user); +} + +$userOption = findUserById(123); + +// We can then handle the optional value using the methods provided by the Option type +if ($userOption->isSome()) { + $user = $userOption->unwrap(); + // Do something with the user +} else { + // Handle the case where no user was found +} +``` + +Now, let's consider a scenario where we have a function that can either succeed or fail. We can use the `Result` type to handle this. + +```php +use Bosunski\Results\Result\Result as ResultInterface; +use function Bosunski\Results\Result; + +function divide(int $numerator, int $denominator): ResultInterface { + if ($denominator == 0) { + return Err(new Exception("Cannot divide by zero")); + } else { + return Ok($numerator / $denominator); + } +} + +$result = divide(10, 0); + +// We can then handle the result using the methods provided by the Result type +if ($result->isOk()) { + $value = $result->unwrap(); + // Do something with the value +} else { + $error = $result->unwrapErr(); + // Handle the error +} +``` + +In these examples, the `Option` and `Result` types provide a way to handle optional values and the results of operations in a safe and expressive manner. + +### The wrap Function + +The `wrap` function is a utility function provided by the library. It is designed to handle operations that can throw an error. The `wrap` function executes a `callable` and returns its result wrapped in a `Result` object. If the callable function throws an exception, the wrap function catches it and returns an `Err` object containing the exception. + +> [!IMPORTANT] +> The error that `wrap` captures depends on your error configuration. Also, `wrap` will only capture instances of `Throwable`. If your function throws something that is not an instance of `Throwable`, it will not be captured by `wrap`. + +Here's an example of how you might use the wrap function: + +```php +use function Bosunski\Results\wrap; + +function mightThrowException(): int { + if (rand(0, 1) === 1) { + throw new Exception('An error occurred'); + } + + return 42; +} + +$result = wrap('mightThrowException'); + +if ($result->isOk()) { + echo "Success: " . $result->unwrap(); +} else { + echo "Error: " . $result->unwrapErr()->getMessage(); +} +``` + +In this example, `mightThrowException` is a function that might throw an exception. We pass this function to `wrap`, which executes it, catch *any* error and wraps the result in a `Result` object. We then check if the result is an instance of `Ok` or `Err` and handle it accordingly. + +The `wrap` function provides a safe and expressive way to handle operations that can throw errors, allowing you to focus on your application logic rather than error handling when you don't need to. + + +## Testing + +You can run `composer run test` to run the unit tests for this library. + +## License + +This project is licensed under the MIT License. \ No newline at end of file diff --git a/src/Option/None.php b/src/Option/None.php index 01be725..18af171 100644 --- a/src/Option/None.php +++ b/src/Option/None.php @@ -54,4 +54,14 @@ public function toResult(Throwable $e): Err { return new Err($e); } + + public function isSome(): bool + { + return false; + } + + public function isNone(): bool + { + return true; + } } diff --git a/src/Option/Option.php b/src/Option/Option.php index 82fa67e..0a80364 100644 --- a/src/Option/Option.php +++ b/src/Option/Option.php @@ -51,4 +51,14 @@ public function map(callable $fn): Option; * @return Result */ public function toResult(Throwable $e): Result; + + /** + * @phpstan-assert-if-true !true $this->isNone() + */ + public function isSome(): bool; + + /** + * @phpstan-assert-if-true !true $this->isSome() + */ + public function isNone(): bool; } diff --git a/src/Option/Some.php b/src/Option/Some.php index 2022730..bbc62f0 100644 --- a/src/Option/Some.php +++ b/src/Option/Some.php @@ -63,4 +63,14 @@ public function toResult(Throwable $e): Ok { return new Ok($this->value); } + + public function isSome(): bool + { + return true; + } + + public function isNone(): bool + { + return false; + } } diff --git a/src/helpers.php b/src/helpers.php index 1191921..f386c79 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -79,14 +79,14 @@ function Result(mixed $value): Ok|Err } } -if (! function_exists('wrapResult')) { +if (! function_exists('wrap')) { /** * @template T * * @param callable(): T $fn * @return Result */ - function wrapResult(callable $fn): Result + function wrap(callable $fn): Result { try { return Ok($fn()); diff --git a/tests/Unit/HelpersTest.php b/tests/Unit/HelpersTest.php index 5723816..5df13a1 100644 --- a/tests/Unit/HelpersTest.php +++ b/tests/Unit/HelpersTest.php @@ -11,7 +11,7 @@ use function Results\Option; use function Results\Result; use function Results\Some; -use function Results\wrapResult; +use function Results\wrap; test('Ok returns an Ok', function (): void { $ok = Ok(1); @@ -60,14 +60,14 @@ }); test('wrapResult() returns Ok if successful', function (): void { - $ok = wrapResult(fn () => 1); + $ok = wrap(fn () => 1); expect($ok)->toBeInstanceOf(Ok::class) ->and($ok->unwrap())->toBe(1); }); test('wrapResult() returns Err if unsuccessful', function (): void { $exception = new Exception('some message'); - $err = wrapResult(fn () => throw $exception); + $err = wrap(fn () => throw $exception); expect($err)->toBeInstanceOf(Err::class) ->and($e = $err->expectErr(''))->toBeInstanceOf(Exception::class) ->and($e)->toBe($exception)