diff --git a/README.md b/README.md index 7438a1f..42e0f6b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ a value is now encoded in its return type. The type shows it usefulness with it's ability to create a "pipeline" operations, catching exceptions along the way. -> Note: this implementation of the Try type is called Attempt, because "try" is +> Note: this implementation of the Try type is called Result, because "try" is > a reserved keyword in PHP. ## Before / after @@ -30,9 +30,9 @@ try { } ``` -After, the `UserService` and `Serializer` now return a response of type `Try` +After, the `UserService` and `Serializer` now return a response of type `Result` meaning that the computation will either be a `Failure` or a `Success`. The -combinators on the `Try` type are used to chain the following code in the case +combinators on the `Result` type are used to chain the following code in the case the previous operation was successful. ```php @@ -57,17 +57,23 @@ or add it to your `composer.json` file. > Note: most of the example code below can be tried out with the > `user-input.php` example from the `examples/` directory. -### Constructing an Attempt +### Constructing an Result -Turn any callable in an `Attempt` using the `Attempt::call()` construct it. +Build a `Result` using the `Attempt::call()` factory, which invokes the callable immediately and handles the outcome, +either returning a `Success` containing the returned value, or a `Failure` containing the thrown exception. ```php -\PhpTry\Attempt::call('callableThatMightThrow', array('argument1', 'argument2')); +$result = \PhpTry\Attempt::call('callableThatMightThrow', array('argument1', 'argument2')); ``` Or use `Success` and `Failure` directly in your API instead of throwing exceptions: ```php +/** + * @param mixed $dividend + * @param mixed $divisor + * @return \PhpTry\Result + */ function divide($dividend, $divisor) { if ($divisor === 0) { return new \PhpTry\Failure(new InvalidArgumentException('Divisor cannot be 0.')); @@ -77,9 +83,9 @@ function divide($dividend, $divisor) { } ``` -### Using combinators on an Attempt +### Using combinators on a Result -Now that we have the `Attempt` object we can use it's combinators to handle the +Now that we have the `Result` object we can use it's combinators to handle the success and failure cases. #### Getting the value @@ -87,7 +93,7 @@ success and failure cases. Gets the value from Success, or throws the original exception if it was a Failure. ```php -$try->get(); +$result->get(); ``` #### Falling back to a default value if Failure @@ -96,17 +102,17 @@ Gets the value from Success, or get a provided alternative if the computation fa ```php // or a provided fallback value -$try->getOrElse(-1); +$result->getOrElse(-1); // or a value returned by the callable // note: if the provided callable throws, this exception will not be catched -$try->getOrCall(function() { return -1; }); +$result->getOrCall(function() { return -1; }); -// or else return another Attempt -$try->orElse(Attempt::call('divide', array(42, 21))); +// or else return another Result +$result->orElse(Attempt::call('divide', array(42, 21))); // or else return Another attempt from a callable -$try->orElseCall('promptDivide'); +$result->orElseCall('promptDivide'); ``` #### Walking the happy path @@ -119,19 +125,19 @@ operation will result in a Failure. ```php // map to Another attempt -$try->flatMap(function($elem) { +$result->flatMap(function($elem) { return Attempt::call('divide', array($elem, promptDivide()->get())); }); // map the success value to another value -$try->map(function($elem) { return $elem * 2; }); +$result->map(function($elem) { return $elem * 2; }); // Success, if the predicate holds for the Success value, Failure otherwise -$try->filter(function($elem) { return $elem === 42; }) +$result->filter(function($elem) { return $elem === 42; }) // only foreachable if success -foreach ($try as $result) { - echo $result; +foreach ($result as $value) { + echo $value; } ``` @@ -143,10 +149,10 @@ When we do care about the Failure path we might want to try and fix things. The ```php // recover with with a value returned by a callable -$try->recover(function($ex) { if ($ex instanceof RuntimeException) { return 21; } throw $ex; }) +$result->recover(function($ex) { if ($ex instanceof RuntimeException) { return 21; } throw $ex; }) // recover with with an attempt returned by a callable -$try->recoverWith(function() { return promptDivide(); }) +$result->recoverWith(function() { return promptDivide(); }) ``` The `recover` and `recoverWith` combinators can be useful when calling for @@ -155,13 +161,13 @@ calling the service again or calling an alternative service. #### Don't call us, we'll call you -The Try type can also call provided callables on a successful or failed computation: +The `Result` type can also call provided callables on a successful or failed computation: ```php -// on* handlers -$try - ->onSuccess(function($elem) { echo "Result of a / b * c is: $elem\n"; }) - ->onFailure(function($elem) { echo "Something went wrong: " . $elem->getMessage() . "\n"; promptDivide(); }) +// if* handlers +$result + ->ifSuccess(function($elem) { echo "Result of a / b * c is: $elem\n"; }) + ->ifFailure(function($elem) { echo "Something went wrong: " . $elem->getMessage() . "\n"; promptDivide(); }) ; ``` @@ -171,17 +177,17 @@ It is possible to execute the provided callable only when needed. This is especially useful when recovering with for example expensive alternatives. ```php -$try->orElse(Attempt::lazily('someExpensiveComputationThatMightThrow')); +$result->orElse(Attempt::lazily('someExpensiveComputationThatMightThrow')); ``` #### Other options -When you have [phpoption/phpoption] installed, the Attempt can be converted to +When you have [phpoption/phpoption] installed, the Result can be converted to an Option. In this mapping a Succes maps to Some and a Failure maps to a None value. ```php -$try->toOption(); // Some(value) or None() +$result->toOption(); // Some(value) or None() ``` [phpoption/phpoption]: https://github.com/schmittjoh/php-option diff --git a/examples/user-input.php b/examples/user-input.php index 7a88a71..a1e015e 100644 --- a/examples/user-input.php +++ b/examples/user-input.php @@ -1,6 +1,6 @@ isSuccess() ? $this->get() : $default; - } - - /** - * Returns the value if it is Success, or the the return value of the callable otherwise. - * - * Note: will throw if the callable throws. - * - * @param callable $callable - * - * @return mixed - */ - public function getOrCall($callable) - { - return $this->isSuccess() ? $this->get() : call_user_func($callable); - } - - /** - * Returns this Attempt if Success, or the given Attempt otherwise. - * - * @param Attempt $try - * - * @return Attempt - */ - public function orElse(Attempt $try) - { - return $this->isSuccess() ? $this : $try; - } - - /** - * Returns this Attempt if Success, or the result of the callable otherwise. - * - * @param callable $callable Callable returning an Attempt. - * - * @return Attempt - */ - public function orElseCall($callable) - { - if ($this->isSuccess()) { - return $this; - } - - try { - $value = call_user_func($callable); - - if ( ! $value instanceof Attempt) { - return new Failure(new UnexpectedValueException('Return value of callable should be an Attempt.')); - } - - return $value; - } catch (Exception $ex) { - return new Failure($ex); - } - } - - /** - * {@inheritDoc} - */ - public function getIterator() - { - if ($this->isSuccess()) { - return new ArrayIterator(array($this->get())); - } else { - return new EmptyIterator(); - } - } - - /** - * Its value if Success, or throws the exception if this is a Failure. - * - * @return mixed - */ - abstract public function get(); - - - /** - * Returns the given function applied to the value from this Success, or returns this if this is a Failure. - * - * Useful for calling Attempting another operation that might throw an - * exception, while an already catched exception gets propagated. - * - * @param callable $callable Callable returning an Attempt. - * - * @return Attempt - */ - abstract public function flatMap($callable); - - /** - * Maps the given function to the value from this Success, or returns this if this is a Failure. - * - * @param callable $callable Callable returning a value. - * - * @return Attempt - */ - abstract public function map($callable); - - /** - * Converts this to a Failure if the predicate is not satisfied. - * - * @param mixed $callable Callable retuning a boolean. - * - * @return Attempt - */ - abstract public function filter($callable); - - /** - * Applies the callable if this is a Failure, otherwise returns if this is a Success. - * - * Note: this is like `flatMap` for the exception. - * - * @param callable $callable Callable taking an exception and returning an Attempt. - * - * @return Attempt - */ - abstract public function recoverWith($callable); - - /** - * Applies the callable if this is a Failure, otherwise returns if this is a Success. - * - * Note: this is like `map` for the exception. - * - * @param callable $callable Callable taking an exception and returning a value. - * - * @return Attempt - */ - abstract public function recover($callable); - - /** - * Callable called when this is a Success. - * - * @param mixed $callable Callable taking a value. - * - * @return void - */ - abstract public function onSuccess($callable); - - /** - * Callable called when this is a Success. - * - * @param mixed $callable Callable taking an exception. - * - * @return void - */ - abstract public function onFailure($callable); - - /** - * Converts the Attempt to an Option. - * - * Returns 'Some' if it is Success, or 'None' if it's a Failure. - * - * @return \PhpOption\Option - */ - abstract public function toOption(); - - /** - * Callable called when this is a Success. - * - * Like `map`, but without caring about the return value of the callable. - * Useful for consuming the possible value of the Attempt. - * - * @return Attempt The current Attempt - */ - abstract public function forAll($callable); - - /** - * Constructs an Attempt by calling the passed callable. + * Constructs a Result by calling the passed callable. * * @param callable $callable * @param array $arguments Optional arguments for the callable. * - * @return Attempt + * @return Result */ public static function call($callable, $arguments = array()) { @@ -220,17 +35,17 @@ public static function call($callable, $arguments = array()) } /** - * Constructs a LazyAttempt by calling the passed callable. + * Constructs a LazyResult by calling the passed callable. * - * The callable will only be called if a method on the Attempt is called. + * The callable will only be called if a method on the Result is called. * * @param callable $callable * @param array $arguments Optional arguments for the callable. * - * @return LazyAttempt + * @return LazyResult */ public static function lazily($callable, $arguments = array()) { - return new LazyAttempt($callable, $arguments); + return new LazyResult($callable, $arguments); } } diff --git a/src/PhpTry/Failure.php b/src/PhpTry/Failure.php index a92b4bf..a4836c3 100644 --- a/src/PhpTry/Failure.php +++ b/src/PhpTry/Failure.php @@ -5,7 +5,7 @@ use Exception; use UnexpectedValueException; -class Failure extends Attempt +class Failure extends Result { private $exception; @@ -49,8 +49,8 @@ public function recoverWith($callable) try { $value = call_user_func_array($callable, array($this->exception)); - if ( ! $value instanceof Attempt) { - return new Failure(new UnexpectedValueException('Return value of callable should be an Attempt.')); + if ( ! $value instanceof Result) { + return new Failure(new UnexpectedValueException('Return value of callable should be a Result.')); } return $value; @@ -64,14 +64,14 @@ public function recover($callable) return Attempt::call($callable, array($this->exception)); } - public function onFailure($callable) + public function ifFailure($callable) { - $value = call_user_func_array($callable, array($this->exception)); + call_user_func_array($callable, array($this->exception)); return $this; } - public function onSuccess($callable) + public function ifSuccess($callable) { return $this; } diff --git a/src/PhpTry/LazyAttempt.php b/src/PhpTry/LazyResult.php similarity index 78% rename from src/PhpTry/LazyAttempt.php rename to src/PhpTry/LazyResult.php index 4445bfb..4b2fe63 100644 --- a/src/PhpTry/LazyAttempt.php +++ b/src/PhpTry/LazyResult.php @@ -3,11 +3,11 @@ namespace PhpTry; /** - * Attempt that is evaluated on first access. + * Result that is evaluated on first access. */ -class LazyAttempt extends Attempt +class LazyResult extends Result { - private $attempt; + private $result; private $callable; private $arguments; @@ -37,7 +37,7 @@ public function getOrCall($callable) return $this->attempt()->getOrCall($callable); } - public function orElse(Attempt $try) + public function orElse(Result $try) { return $this->attempt()->orElse($try); } @@ -82,24 +82,23 @@ public function recover($callable) return $this->attempt()->recover($callable); } - public function onSuccess($callable) + public function ifSuccess($callable) { - return $this->attempt()->onSuccess($callable); - + return $this->attempt()->ifSuccess($callable); } - public function onFailure($callable) + public function ifFailure($callable) { - return $this->attempt()->onFailure($callable); + return $this->attempt()->ifFailure($callable); } private function attempt() { - if (null === $this->attempt) { - return $this->attempt = Attempt::call($this->callable, $this->arguments); + if (null === $this->result) { + return $this->result = Attempt::call($this->callable, $this->arguments); } - return $this->attempt; + return $this->result; } public function toOption() diff --git a/src/PhpTry/Result.php b/src/PhpTry/Result.php new file mode 100644 index 0000000..8169461 --- /dev/null +++ b/src/PhpTry/Result.php @@ -0,0 +1,202 @@ +isSuccess() ? $this->get() : $default; + } + + /** + * Returns the value if it is Success, or the the return value of the callable otherwise. + * + * Note: will throw if the callable throws. + * + * @param callable $callable + * + * @return mixed + */ + public function getOrCall($callable) + { + return $this->isSuccess() ? $this->get() : call_user_func($callable); + } + + /** + * Returns this Result if Success, or the given Result otherwise. + * + * @param Result $try + * + * @return Result + */ + public function orElse(Result $try) + { + return $this->isSuccess() ? $this : $try; + } + + /** + * Returns this Result if Success, or the result of the callable otherwise. + * + * @param callable $callable Callable returning a Result. + * + * @return Result + */ + public function orElseCall($callable) + { + if ($this->isSuccess()) { + return $this; + } + + try { + $value = call_user_func($callable); + + if ( ! $value instanceof Result) { + return new Failure(new UnexpectedValueException('Return value of callable should be a Result.')); + } + + return $value; + } catch (Exception $ex) { + return new Failure($ex); + } + } + + /** + * {@inheritDoc} + */ + public function getIterator() + { + if ($this->isSuccess()) { + return new ArrayIterator(array($this->get())); + } else { + return new EmptyIterator(); + } + } + + /** + * Its value if Success, or throws the exception if this is a Failure. + * + * @return mixed + */ + abstract public function get(); + + + /** + * Returns the given function applied to the value from this Success, or returns this if this is a Failure. + * + * Useful for calling Attempting another operation that might throw an + * exception, while an already catched exception gets propagated. + * + * @param callable $callable Callable returning a Result. + * + * @return Result + */ + abstract public function flatMap($callable); + + /** + * Maps the given function to the value from this Success, or returns this if this is a Failure. + * + * @param callable $callable Callable returning a value. + * + * @return Result + */ + abstract public function map($callable); + + /** + * Converts this to a Failure if the predicate is not satisfied. + * + * @param mixed $callable Callable retuning a boolean. + * + * @return Result + */ + abstract public function filter($callable); + + /** + * Applies the callable if this is a Failure, otherwise returns if this is a Success. + * + * Note: this is like `flatMap` for the exception. + * + * @param callable $callable Callable taking an exception and returning a Result. + * + * @return Result + */ + abstract public function recoverWith($callable); + + /** + * Applies the callable if this is a Failure, otherwise returns if this is a Success. + * + * Note: this is like `map` for the exception. + * + * @param callable $callable Callable taking an exception and returning a value. + * + * @return Result + */ + abstract public function recover($callable); + + /** + * Callable called when this is a Success. + * + * @param mixed $callable Callable taking a value. + * + * @return void + */ + abstract public function ifSuccess($callable); + + /** + * Callable called when this is a Success. + * + * @param mixed $callable Callable taking an exception. + * + * @return void + */ + abstract public function ifFailure($callable); + + /** + * Converts the Result to an Option. + * + * Returns 'Some' if it is Success, or 'None' if it's a Failure. + * + * @return \PhpOption\Option + */ + abstract public function toOption(); + + /** + * Callable called when this is a Success. + * + * Like `map`, but without caring about the return value of the callable. + * Useful for consuming the possible value of the Result. + * + * @param callable $callable Callable called regardless of the result type + * + * @return Result The current Result + */ + abstract public function forAll($callable); +} diff --git a/src/PhpTry/Success.php b/src/PhpTry/Success.php index 4c78891..d13cb17 100644 --- a/src/PhpTry/Success.php +++ b/src/PhpTry/Success.php @@ -3,10 +3,9 @@ namespace PhpTry; use Exception; -use RuntimeException; use UnexpectedValueException; -class Success extends Attempt +class Success extends Result { private $value; @@ -35,8 +34,8 @@ public function flatMap($callable) try { $value = call_user_func_array($callable, array($this->value)); - if ( ! $value instanceof Attempt) { - return new Failure(new UnexpectedValueException('Return value of callable should be an Attempt.')); + if ( ! $value instanceof Result) { + return new Failure(new UnexpectedValueException('Return value of callable should be a Result.')); } return $value; @@ -75,14 +74,14 @@ public function recover($callable) return $this; } - public function onFailure($callable) + public function ifFailure($callable) { return $this; } - public function onSuccess($callable) + public function ifSuccess($callable) { - $value = call_user_func_array($callable, array($this->value)); + call_user_func_array($callable, array($this->value)); return $this; } diff --git a/tests/PhpTry/FailedAttemptTestCase.php b/tests/PhpTry/FailedAttemptTestCase.php index 2659860..fcb3c42 100644 --- a/tests/PhpTry/FailedAttemptTestCase.php +++ b/tests/PhpTry/FailedAttemptTestCase.php @@ -121,7 +121,7 @@ public function it_does_not_return_an_element_in_foreach() * @test * @dataProvider attemptValues */ - public function it_returns_the_result_of_the_callable_on_recoverWith(Attempt $attempt) + public function it_returns_the_result_of_the_callable_on_recoverWith(Result $attempt) { $this->assertSame($attempt, $this->failure->recoverWith(function() use ($attempt) { return $attempt; })); } @@ -199,11 +199,11 @@ public function it_returns_Failure_if_the_recover_callable_throws() /** * @test */ - public function it_passes_its_value_to_the_onFailure_callable() + public function it_passes_its_value_to_the_ifFailure_callable() { $value = null; - $this->failure->onFailure(function($elem) use (&$value) { $value = $elem; }); + $this->failure->ifFailure(function($elem) use (&$value) { $value = $elem; }); $this->assertEquals($this->exception, $value); } @@ -211,11 +211,11 @@ public function it_passes_its_value_to_the_onFailure_callable() /** * @test */ - public function it_passes_does_not_call_onSuccess() + public function it_passes_does_not_call_ifSuccess() { $called = false; - $this->failure->onSuccess(function() use (&$called) { $called = true; }); + $this->failure->ifSuccess(function() use (&$called) { $called = true; }); $this->assertFalse($called); } diff --git a/tests/PhpTry/FailureTest.php b/tests/PhpTry/FailureTest.php index c95b8d6..f005ea5 100644 --- a/tests/PhpTry/FailureTest.php +++ b/tests/PhpTry/FailureTest.php @@ -39,17 +39,17 @@ public function it_returns_itself_on_filter() /** * @test */ - public function it_returns_itself_onFailure() + public function it_returns_itself_ifFailure() { - $this->assertSame($this->failure, $this->failure->onFailure(function() {})); + $this->assertSame($this->failure, $this->failure->ifFailure(function() {})); } /** * @test */ - public function it_returns_itself_onSuccess() + public function it_returns_itself_ifSuccess() { - $this->assertSame($this->failure, $this->failure->onSuccess(function() {})); + $this->assertSame($this->failure, $this->failure->ifSuccess(function() {})); } /** diff --git a/tests/PhpTry/LazyAttemptFailureTest.php b/tests/PhpTry/LazyAttemptFailureTest.php index 8ea0255..de7b441 100644 --- a/tests/PhpTry/LazyAttemptFailureTest.php +++ b/tests/PhpTry/LazyAttemptFailureTest.php @@ -2,7 +2,6 @@ namespace PhpTry; -use Exception; use PHPUnit_Framework_TestCase; class LazyAttemptFailureTest extends FailedAttemptTestCase diff --git a/tests/PhpTry/LazyAttemptSuccessfulTest.php b/tests/PhpTry/LazyAttemptSuccessfulTest.php index 700a9dc..6ff8726 100644 --- a/tests/PhpTry/LazyAttemptSuccessfulTest.php +++ b/tests/PhpTry/LazyAttemptSuccessfulTest.php @@ -2,7 +2,6 @@ namespace PhpTry; -use Exception; use PHPUnit_Framework_TestCase; class LazyAttemptSuccessfulTest extends SuccessfulAttemptTestCase diff --git a/tests/PhpTry/LazyAttemptTest.php b/tests/PhpTry/LazyAttemptTest.php index 2b8c058..a57db33 100644 --- a/tests/PhpTry/LazyAttemptTest.php +++ b/tests/PhpTry/LazyAttemptTest.php @@ -2,7 +2,6 @@ namespace PhpTry; -use Exception; use PHPUnit_Framework_TestCase; class LazyAttemptTest extends PHPUnit_Framework_TestCase diff --git a/tests/PhpTry/SuccessTest.php b/tests/PhpTry/SuccessTest.php index f0adb17..41121e5 100644 --- a/tests/PhpTry/SuccessTest.php +++ b/tests/PhpTry/SuccessTest.php @@ -55,16 +55,16 @@ public function it_returns_itself_on_recover() /** * @test */ - public function it_returns_itself_onSuccess() + public function it_returns_itself_ifSuccess() { - $this->assertSame($this->success, $this->success->onSuccess(function() {})); + $this->assertSame($this->success, $this->success->ifSuccess(function() {})); } /** * @test */ - public function it_returns_itself_onFailure() + public function it_returns_itself_ifFailure() { - $this->assertSame($this->success, $this->success->onFailure(function() {})); + $this->assertSame($this->success, $this->success->ifFailure(function() {})); } } diff --git a/tests/PhpTry/SuccessfulAttemptTestCase.php b/tests/PhpTry/SuccessfulAttemptTestCase.php index b2638f7..411f5cf 100644 --- a/tests/PhpTry/SuccessfulAttemptTestCase.php +++ b/tests/PhpTry/SuccessfulAttemptTestCase.php @@ -75,7 +75,7 @@ public function it_returns_the_element_with_foreach() * @test * @dataProvider attemptValues */ - public function it_returns_the_result_of_the_callable_on_flatMap(Attempt $attempt) + public function it_returns_the_result_of_the_callable_on_flatMap(Result $attempt) { $this->assertSame($attempt, $this->success->flatMap(function() use ($attempt) { return $attempt; })); } @@ -184,11 +184,11 @@ public function it_returns_Failure_if_the_filter_callable_throws() /** * @test */ - public function it_passes_its_value_to_the_onSuccess_callable() + public function it_passes_its_value_to_the_ifSuccess_callable() { $value = null; - $this->success->onSuccess(function($elem) use (&$value) { $value = $elem; }); + $this->success->ifSuccess(function($elem) use (&$value) { $value = $elem; }); $this->assertEquals($this->success->get(), $value); } @@ -196,11 +196,11 @@ public function it_passes_its_value_to_the_onSuccess_callable() /** * @test */ - public function it_passes_does_not_call_onFailure() + public function it_passes_does_not_call_ifFailure() { $called = false; - $this->success->onFailure(function() use (&$called) { $called = true; }); + $this->success->ifFailure(function() use (&$called) { $called = true; }); $this->assertFalse($called); }