├── .gitignore
├── .travis.yml
├── src
└── PhpTry
│ ├── NoSuchElementException.php
│ ├── Failure.php
│ ├── Success.php
│ ├── LazyAttempt.php
│ └── Attempt.php
├── tests
├── bootstrap.php
└── PhpTry
│ ├── LazyAttemptFailureTest.php
│ ├── LazyAttemptSuccessfulTest.php
│ ├── LazyAttemptTest.php
│ ├── AttemptTest.php
│ ├── FailureTest.php
│ ├── SuccessTest.php
│ ├── SuccessfulAttemptTestCase.php
│ └── FailedAttemptTestCase.php
├── phpunit.xml.dist
├── composer.json
├── LICENSE
├── examples
└── user-input.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | phpunit.xml
2 | composer.lock
3 | composer.phar
4 | /vendor/
5 | tags
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.3
5 | - 5.4
6 | - 5.5
7 | - hhvm
8 |
9 | before_script: composer install
10 |
11 | script: phpunit
12 |
--------------------------------------------------------------------------------
/src/PhpTry/NoSuchElementException.php:
--------------------------------------------------------------------------------
1 | add('PhpTry', __DIR__);
6 | } else {
7 | throw new RuntimeException('Install dependencies to run test suite.');
8 | }
9 |
--------------------------------------------------------------------------------
/tests/PhpTry/LazyAttemptFailureTest.php:
--------------------------------------------------------------------------------
1 | assertFalse($called);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/PhpTry/AttemptTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf('PhpTry\Success', $success);
18 | }
19 |
20 | /**
21 | * @test
22 | */
23 | public function it_returns_Failure_if_the_callable_thrown()
24 | {
25 | $failure = Attempt::call(function() { throw new Exception(); });
26 |
27 | $this->assertInstanceOf('PhpTry\Failure', $failure);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 | ./tests/PhpTry/
17 |
18 |
19 |
20 |
21 |
22 | ./src/
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phptry/phptry",
3 | "description": "Try type for PHP",
4 | "keywords": ["try", "success", "failure", "monad"],
5 | "type": "library",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Alexander",
10 | "email": "iam.asm89@gmail.com"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=5.3.2"
15 | },
16 | "require-dev": {
17 | "phpoption/phpoption": "1.*"
18 | },
19 | "autoload": {
20 | "psr-0": { "PhpTry\\": "src/" }
21 | },
22 | "suggest": {
23 | "phpoption/phpoption": "If you want to turn your success/failure into an option."
24 | },
25 | "extra": {
26 | "branch-alias": {
27 | "dev-master": "0.1-dev"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Alexander
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do 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
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/tests/PhpTry/FailureTest.php:
--------------------------------------------------------------------------------
1 | exception);
13 | }
14 |
15 | /**
16 | * @test
17 | */
18 | public function it_returns_itself_on_flatMap()
19 | {
20 | $this->assertSame($this->failure, $this->failure->flatMap(function(){}));
21 | }
22 |
23 | /**
24 | * @test
25 | */
26 | public function it_returns_itself_on_map()
27 | {
28 | $this->assertSame($this->failure, $this->failure->map(function(){}));
29 | }
30 |
31 | /**
32 | * @test
33 | */
34 | public function it_returns_itself_on_filter()
35 | {
36 | $this->assertSame($this->failure, $this->failure->filter(function(){}));
37 | }
38 |
39 | /**
40 | * @test
41 | */
42 | public function it_returns_itself_onFailure()
43 | {
44 | $this->assertSame($this->failure, $this->failure->onFailure(function() {}));
45 | }
46 |
47 | /**
48 | * @test
49 | */
50 | public function it_returns_itself_onSuccess()
51 | {
52 | $this->assertSame($this->failure, $this->failure->onSuccess(function() {}));
53 | }
54 |
55 | /**
56 | * @test
57 | */
58 | public function it_returns_itself_forAll()
59 | {
60 | $this->assertSame($this->failure, $this->failure->forAll(function() {}));
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/PhpTry/SuccessTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($this->success, $this->success->orElse(new Failure(new Exception())));
21 | }
22 |
23 | /**
24 | * @test
25 | */
26 | public function it_returns_itself_on_orElseCall()
27 | {
28 | $this->assertEquals($this->success, $this->success->orElseCall(function (){ return 21; }));
29 | }
30 |
31 | /**
32 | * @test
33 | */
34 | public function it_returns_itself_if_the_callable_returns_true_on_filter()
35 | {
36 | $this->assertSame($this->success, $this->success->filter(function() { return true; }));
37 | }
38 |
39 | /**
40 | * @test
41 | */
42 | public function it_returns_itself_on_recoverWith()
43 | {
44 | $this->assertSame($this->success, $this->success->recoverWith(function(){}));
45 | }
46 |
47 | /**
48 | * @test
49 | */
50 | public function it_returns_itself_on_recover()
51 | {
52 | $this->assertSame($this->success, $this->success->recover(function(){}));
53 | }
54 |
55 | /**
56 | * @test
57 | */
58 | public function it_returns_itself_onSuccess()
59 | {
60 | $this->assertSame($this->success, $this->success->onSuccess(function() {}));
61 | }
62 |
63 | /**
64 | * @test
65 | */
66 | public function it_returns_itself_onFailure()
67 | {
68 | $this->assertSame($this->success, $this->success->onFailure(function() {}));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/PhpTry/Failure.php:
--------------------------------------------------------------------------------
1 | exception = $exception;
15 | }
16 |
17 | public function isFailure()
18 | {
19 | return true;
20 | }
21 |
22 | public function isSuccess()
23 | {
24 | return false;
25 | }
26 |
27 | public function get()
28 | {
29 | throw $this->exception;
30 | }
31 |
32 | public function flatMap($callable)
33 | {
34 | return $this;
35 | }
36 |
37 | public function map($callable)
38 | {
39 | return $this;
40 | }
41 |
42 | public function filter($callable)
43 | {
44 | return $this;
45 | }
46 |
47 | public function recoverWith($callable)
48 | {
49 | try {
50 | $value = call_user_func_array($callable, array($this->exception));
51 |
52 | if ( ! $value instanceof Attempt) {
53 | return new Failure(new UnexpectedValueException('Return value of callable should be an Attempt.'));
54 | }
55 |
56 | return $value;
57 | } catch (Exception $ex) {
58 | return new Failure($ex);
59 | }
60 | }
61 |
62 | public function recover($callable)
63 | {
64 | return Attempt::call($callable, array($this->exception));
65 | }
66 |
67 | public function onFailure($callable)
68 | {
69 | $value = call_user_func_array($callable, array($this->exception));
70 |
71 | return $this;
72 | }
73 |
74 | public function onSuccess($callable)
75 | {
76 | return $this;
77 | }
78 |
79 | public function forAll($callable)
80 | {
81 | return $this;
82 | }
83 |
84 | public function toOption()
85 | {
86 | return \PhpOption\None::create();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/PhpTry/Success.php:
--------------------------------------------------------------------------------
1 | value = $value;
16 | }
17 |
18 | public function isFailure()
19 | {
20 | return false;
21 | }
22 |
23 | public function isSuccess()
24 | {
25 | return true;
26 | }
27 |
28 | public function get()
29 | {
30 | return $this->value;
31 | }
32 |
33 | public function flatMap($callable)
34 | {
35 | try {
36 | $value = call_user_func_array($callable, array($this->value));
37 |
38 | if ( ! $value instanceof Attempt) {
39 | return new Failure(new UnexpectedValueException('Return value of callable should be an Attempt.'));
40 | }
41 |
42 | return $value;
43 | } catch (Exception $ex) {
44 | return new Failure($ex);
45 | }
46 | }
47 |
48 | public function map($callable)
49 | {
50 | return Attempt::call($callable, array($this->value));
51 | }
52 |
53 | public function filter($callable)
54 | {
55 | try {
56 | $value = call_user_func_array($callable, array($this->value));
57 |
58 | if ($value) {
59 | return $this;
60 | }
61 |
62 | return new Failure(new NoSuchElementException('Predicate does not hold for ' . $this->value));
63 | } catch (Exception $ex) {
64 | return new Failure($ex);
65 | }
66 | }
67 |
68 | public function recoverWith($callable)
69 | {
70 | return $this;
71 | }
72 |
73 | public function recover($callable)
74 | {
75 | return $this;
76 | }
77 |
78 | public function onFailure($callable)
79 | {
80 | return $this;
81 | }
82 |
83 | public function onSuccess($callable)
84 | {
85 | $value = call_user_func_array($callable, array($this->value));
86 |
87 | return $this;
88 | }
89 |
90 | public function toOption()
91 | {
92 | return new \PhpOption\Some($this->value);
93 | }
94 |
95 | public function forAll($callable)
96 | {
97 | call_user_func($callable, $this->value);
98 |
99 | return $this;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/PhpTry/LazyAttempt.php:
--------------------------------------------------------------------------------
1 | callable = $callable;
17 | $this->arguments = $arguments;
18 | }
19 |
20 | public function isFailure()
21 | {
22 | return $this->attempt()->isFailure();
23 | }
24 |
25 | public function isSuccess()
26 | {
27 | return $this->attempt()->isSuccess();
28 | }
29 |
30 | public function getOrElse($default)
31 | {
32 | return $this->attempt()->getOrElse($default);
33 | }
34 |
35 | public function getOrCall($callable)
36 | {
37 | return $this->attempt()->getOrCall($callable);
38 | }
39 |
40 | public function orElse(Attempt $try)
41 | {
42 | return $this->attempt()->orElse($try);
43 | }
44 |
45 | public function orElseCall($callable)
46 | {
47 | return $this->attempt()->orElseCall($callable);
48 | }
49 |
50 | public function getIterator()
51 | {
52 | return $this->attempt()->getIterator();
53 | }
54 |
55 | public function get()
56 | {
57 | return $this->attempt()->get();
58 | }
59 |
60 | public function flatMap($callable)
61 | {
62 | return $this->attempt()->flatMap($callable);
63 | }
64 |
65 | public function map($callable)
66 | {
67 | return $this->attempt()->map($callable);
68 | }
69 |
70 | public function filter($callable)
71 | {
72 | return $this->attempt()->filter($callable);
73 | }
74 |
75 | public function recoverWith($callable)
76 | {
77 | return $this->attempt()->recoverWith($callable);
78 | }
79 |
80 | public function recover($callable)
81 | {
82 | return $this->attempt()->recover($callable);
83 | }
84 |
85 | public function onSuccess($callable)
86 | {
87 | return $this->attempt()->onSuccess($callable);
88 |
89 | }
90 |
91 | public function onFailure($callable)
92 | {
93 | return $this->attempt()->onFailure($callable);
94 | }
95 |
96 | private function attempt()
97 | {
98 | if (null === $this->attempt) {
99 | return $this->attempt = Attempt::call($this->callable, $this->arguments);
100 | }
101 |
102 | return $this->attempt;
103 | }
104 |
105 | public function toOption()
106 | {
107 | return $this->attempt()->toOption();
108 | }
109 |
110 | public function forAll($callable)
111 | {
112 | return $this->attempt()->forAll($callable);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/examples/user-input.php:
--------------------------------------------------------------------------------
1 | map(function($elem) use ($c) { return multiply($elem, $c); });
41 | }
42 |
43 | $try = promptDivide();
44 |
45 | // on* handlers
46 | $try
47 | ->onSuccess(function($elem) { echo "Result of a / b * c is: $elem\n"; })
48 | ->onFailure(function($elem) { echo "Something went wrong: " . $elem->getMessage() . "\n"; promptDivide(); })
49 | ;
50 |
51 | //// get() the value, will throw
52 | //echo $try->get();
53 | //
54 | //// return a default value if Failure
55 | //echo $try->getOrElse(-1);
56 | //
57 | //// return a default value returned by a function if Failure
58 | //echo $try->getOrCall(function() { return -1; });
59 | //
60 | //// or else return another attempt
61 | //echo $try->orElse(Attempt::call('divide', array(42, 21)))->get();
62 | //
63 | //// or else return another attempt from a callable
64 | //echo $try->orElseCall('promptDivide')->get();
65 | //
66 | //// only foreachable if success
67 | //foreach ($try as $result) {
68 | // echo $result;
69 | //}
70 | //
71 | //// map with another attempt
72 | //echo $try->flatMap(function($elem) {
73 | // return Attempt::call('divide', array($elem, promptDivide()->get()));
74 | //})->get();
75 | //
76 | //// map the success value to another value
77 | //echo $try->map(function($elem) { return $elem * 2; })->get();
78 | //
79 | //// filter the success value, returns Failure if it doesn't match the filter
80 | //echo $try->filter(function($elem) { return $elem === 42; })->get();
81 | //
82 | //// recover with with a value returned by a callable
83 | //echo $try
84 | // ->filter(function($elem) { return $elem === 42; })
85 | // ->recover(function($ex) { if ($ex instanceof RuntimeException) { return 21; } throw $ex; })
86 | // ->get();
87 | //
88 | //// recover with with an attempt returned by a callable
89 | //echo $try
90 | // ->filter(function($elem) { return $elem === 42; })
91 | // ->recoverWith(function() { return promptDivide(); })
92 | // ->get();
93 |
--------------------------------------------------------------------------------
/tests/PhpTry/SuccessfulAttemptTestCase.php:
--------------------------------------------------------------------------------
1 | success = $this->createSuccess(42);
15 | }
16 |
17 | abstract protected function createSuccess($value);
18 |
19 | /**
20 | * @test
21 | */
22 | public function it_is_success()
23 | {
24 | $this->assertTrue($this->success->isSuccess());
25 | }
26 |
27 | /**
28 | * @test
29 | */
30 | public function it_is_not_failure()
31 | {
32 | $this->assertFalse($this->success->isFailure());
33 | }
34 |
35 | /**
36 | * @test
37 | */
38 | public function it_returns_the_wrapped_value()
39 | {
40 | $this->assertEquals(42, $this->success->get());
41 | }
42 |
43 | /**
44 | * @test
45 | */
46 | public function it_returns_the_wrapped_value_on_getOrElse()
47 | {
48 | $this->assertEquals(42, $this->success->getOrElse(21));
49 |
50 | }
51 |
52 | /**
53 | * @test
54 | */
55 | public function it_returns_the_wrapped_value_on_getOrCall()
56 | {
57 | $this->assertEquals(42, $this->success->getOrCall(function(){ return 21; }));
58 | }
59 |
60 | /**
61 | * @test
62 | */
63 | public function it_returns_the_element_with_foreach()
64 | {
65 | $called = 0;
66 | foreach ($this->success as $elem) {
67 | $this->assertEquals(42, $elem);
68 | $called++;
69 | }
70 |
71 | $this->assertEquals(1, $called);
72 | }
73 |
74 | /**
75 | * @test
76 | * @dataProvider attemptValues
77 | */
78 | public function it_returns_the_result_of_the_callable_on_flatMap(Attempt $attempt)
79 | {
80 | $this->assertSame($attempt, $this->success->flatMap(function() use ($attempt) { return $attempt; }));
81 | }
82 |
83 | public function attemptValues()
84 | {
85 | return array(
86 | array(new Success(42)),
87 | array(new Failure(new Exception())),
88 | );
89 | }
90 |
91 | /**
92 | * @test
93 | */
94 | public function it_passes_its_value_to_the_flatMap_callable()
95 | {
96 | $value = null;
97 |
98 | $this->success->flatMap(function($elem) use (&$value) { $value = $elem; return new Success(21); });
99 |
100 | $this->assertEquals($this->success->get(), $value);
101 | }
102 |
103 | /**
104 | * @test
105 | */
106 | public function it_returns_Failure_if_the_flatMap_callable_throws()
107 | {
108 | $result = $this->success->flatMap(function() { throw new Exception('meh.'); });
109 |
110 | $this->assertInstanceOf('PhpTry\\Failure', $result);
111 | }
112 |
113 | /**
114 | * @test
115 | */
116 | public function it_returns_Failure_if_the_flatMap_callable_does_not_return_an_Attempt()
117 | {
118 | $result = $this->success->flatMap(function() { return 21; });
119 |
120 | $this->assertInstanceOf('PhpTry\\Failure', $result);
121 | }
122 |
123 | /**
124 | * @test
125 | */
126 | public function it_returns_the_result_of_the_callable_on_map()
127 | {
128 | $this->assertEquals(new Success(21), $this->success->map(function() { return 21; }));
129 | }
130 |
131 | /**
132 | * @test
133 | */
134 | public function it_passes_its_value_to_the_map_callable()
135 | {
136 | $value = null;
137 |
138 | $this->success->map(function($elem) use (&$value) { $value = $elem; return 21; });
139 |
140 | $this->assertEquals($this->success->get(), $value);
141 | }
142 |
143 | /**
144 | * @test
145 | */
146 | public function it_returns_Failure_if_the_map_callable_throws()
147 | {
148 | $result = $this->success->map(function() { throw new Exception('meh.'); });
149 |
150 | $this->assertInstanceOf('PhpTry\\Failure', $result);
151 | }
152 |
153 | /**
154 | * @test
155 | */
156 | public function it_returns_Failure_if_the_callable_returns_false_on_filter()
157 | {
158 | $result = $this->success->filter(function() { return false; });
159 | $this->assertInstanceOf('PhpTry\\Failure', $result);
160 | }
161 |
162 | /**
163 | * @test
164 | */
165 | public function it_passes_its_value_to_the_filter_callable()
166 | {
167 | $value = null;
168 |
169 | $this->success->filter(function($elem) use (&$value) { $value = $elem; return true; });
170 |
171 | $this->assertEquals($this->success->get(), $value);
172 | }
173 |
174 | /**
175 | * @test
176 | */
177 | public function it_returns_Failure_if_the_filter_callable_throws()
178 | {
179 | $result = $this->success->filter(function() { throw new Exception('meh.'); });
180 |
181 | $this->assertInstanceOf('PhpTry\\Failure', $result);
182 | }
183 |
184 | /**
185 | * @test
186 | */
187 | public function it_passes_its_value_to_the_onSuccess_callable()
188 | {
189 | $value = null;
190 |
191 | $this->success->onSuccess(function($elem) use (&$value) { $value = $elem; });
192 |
193 | $this->assertEquals($this->success->get(), $value);
194 | }
195 |
196 | /**
197 | * @test
198 | */
199 | public function it_passes_does_not_call_onFailure()
200 | {
201 | $called = false;
202 |
203 | $this->success->onFailure(function() use (&$called) { $called = true; });
204 |
205 | $this->assertFalse($called);
206 | }
207 |
208 | /**
209 | * @test
210 | */
211 | public function it_can_be_converted_to_a_Some_option()
212 | {
213 | $this->assertEquals(new \PhpOption\Some(42), $this->success->toOption());
214 | }
215 |
216 | /**
217 | * @test
218 | */
219 | public function it_calls_on_forAll()
220 | {
221 | $called = false;
222 | $value = null;
223 |
224 | $this->success->forAll(function($v) use (&$called, &$value) { $called = true; $value = $v; });
225 |
226 | $this->assertTrue($called);
227 | $this->assertEquals(42, $value);
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Php Try type
2 | ============
3 |
4 | A Try type for PHP.
5 |
6 | The Try type is useful when called code will either return a value (Success) or
7 | throw an exception (Failure). Instead of relying on the `try {} catch {}`
8 | mechanism to handle this cases, the fact that code might either throw or return
9 | a value is now encoded in its return type.
10 |
11 | The type shows it usefulness with it's ability to create a "pipeline"
12 | operations, catching exceptions along the way.
13 |
14 | > Note: this implementation of the Try type is called Attempt, because "try" is
15 | > a reserved keyword in PHP.
16 |
17 | ## Before / after
18 |
19 | Before, the `UserService` and `Serializer` code might throw exceptions, so we
20 | have an explicit try/catch:
21 |
22 | ```php
23 | try {
24 | $user = $userService->findBy($id);
25 | $responseBody = $this->serializeUser($user);
26 |
27 | return new Response($user);
28 | } catch (Exception $ex) {
29 | return Response('error', 500);
30 | }
31 | ```
32 |
33 | After, the `UserService` and `Serializer` now return a response of type `Try`
34 | meaning that the computation will either be a `Failure` or a `Success`. The
35 | combinators on the `Try` type are used to chain the following code in the case
36 | the previous operation was successful.
37 |
38 | ```php
39 | return $userService->findBy($id)
40 | ->flatMap(function($user) { return $this->serializeUser($user); }) // walk the happy path!
41 | ->map(function($responseBody) { return new Response($responseBody); })
42 | ->recover(function($ex) { return new Response('error', 500); })
43 | ->get(); // returns the wrapped value
44 | ```
45 |
46 | ## Installation
47 |
48 | Run:
49 |
50 | ```
51 | composer require phptry/phptry
52 | ```
53 | or add it to your `composer.json` file.
54 |
55 | ## Usage
56 |
57 | > Note: most of the example code below can be tried out with the
58 | > `user-input.php` example from the `examples/` directory.
59 |
60 | ### Constructing an Attempt
61 |
62 | Turn any callable in an `Attempt` using the `Attempt::call()` construct it.
63 |
64 | ```php
65 | \PhpTry\Attempt::call('callableThatMightThrow', array('argument1', 'argument2'));
66 | ```
67 |
68 | Or use `Success` and `Failure` directly in your API instead of throwing exceptions:
69 |
70 | ```php
71 | function divide($dividend, $divisor) {
72 | if ($divisor === 0) {
73 | return new \PhpTry\Failure(new InvalidArgumentException('Divisor cannot be 0.'));
74 | }
75 |
76 | return new \PhpTry\Success($dividend / $divisor);
77 | }
78 | ```
79 |
80 | ### Using combinators on an Attempt
81 |
82 | Now that we have the `Attempt` object we can use it's combinators to handle the
83 | success and failure cases.
84 |
85 | #### Getting the value
86 |
87 | Gets the value from Success, or throws the original exception if it was a Failure.
88 |
89 | ```php
90 | $try->get();
91 | ```
92 |
93 | #### Falling back to a default value if Failure
94 |
95 | Gets the value from Success, or get a provided alternative if the computation failed.
96 |
97 | ```php
98 | // or a provided fallback value
99 | $try->getOrElse(-1);
100 |
101 | // or a value returned by the callable
102 | // note: if the provided callable throws, this exception will not be catched
103 | $try->getOrCall(function() { return -1; });
104 |
105 | // or else return another Attempt
106 | $try->orElse(Attempt::call('divide', array(42, 21)));
107 |
108 | // or else return Another attempt from a callable
109 | $try->orElseCall('promptDivide');
110 | ```
111 |
112 | #### Walking the happy path
113 |
114 | Sometimes you care about the Success path and want to propagate or even ignore
115 | Failure. The `filter`, `flatMap` and `map` operators shown below will execute
116 | the given code if the previous computation was a Success, or propagate the
117 | Failure otherwise. If the function passed to `flatMap` or `map` throws, the
118 | operation will result in a Failure.
119 |
120 | ```php
121 | // map to Another attempt
122 | $try->flatMap(function($elem) {
123 | return Attempt::call('divide', array($elem, promptDivide()->get()));
124 | });
125 |
126 | // map the success value to another value
127 | $try->map(function($elem) { return $elem * 2; });
128 |
129 | // Success, if the predicate holds for the Success value, Failure otherwise
130 | $try->filter(function($elem) { return $elem === 42; })
131 |
132 | // only foreachable if success
133 | foreach ($try as $result) {
134 | echo $result;
135 | }
136 | ```
137 |
138 | #### Recovering from failure
139 |
140 | When we do care about the Failure path we might want to try and fix things. The
141 | `recover` and `recoverWith` operations are for Failure, what `flatMap` and
142 | `map` are for Success.
143 |
144 | ```php
145 | // recover with with a value returned by a callable
146 | $try->recover(function($ex) { if ($ex instanceof RuntimeException) { return 21; } throw $ex; })
147 |
148 | // recover with with an attempt returned by a callable
149 | $try->recoverWith(function() { return promptDivide(); })
150 | ```
151 |
152 | The `recover` and `recoverWith` combinators can be useful when calling for
153 | example http services that might fail. A failed call can be recovered by
154 | calling the service again or calling an alternative service.
155 |
156 | #### Don't call us, we'll call you
157 |
158 | The Try type can also call provided callables on a successful or failed computation:
159 |
160 | ```php
161 | // on* handlers
162 | $try
163 | ->onSuccess(function($elem) { echo "Result of a / b * c is: $elem\n"; })
164 | ->onFailure(function($elem) { echo "Something went wrong: " . $elem->getMessage() . "\n"; promptDivide(); })
165 | ;
166 | ```
167 |
168 | #### Lazily executed Attempts
169 |
170 | It is possible to execute the provided callable only when needed. This is
171 | especially useful when recovering with for example expensive alternatives.
172 |
173 | ```php
174 | $try->orElse(Attempt::lazily('someExpensiveComputationThatMightThrow'));
175 | ```
176 |
177 | #### Other options
178 |
179 | When you have [phpoption/phpoption] installed, the Attempt can be converted to
180 | an Option. In this mapping a Succes maps to Some and a Failure maps to a None
181 | value.
182 |
183 | ```php
184 | $try->toOption(); // Some(value) or None()
185 | ```
186 |
187 | [phpoption/phpoption]: https://github.com/schmittjoh/php-option
188 |
189 | ## Inspiration
190 |
191 | - Implementation and general idea is based on scala's [Try]
192 | - Schmittjoh's [Option type] for PHP
193 |
194 | [Try]: http://www.scala-lang.org/api/2.9.3/scala/util/Try.html
195 | [Option type]: https://github.com/schmittjoh/php-option
196 |
--------------------------------------------------------------------------------
/tests/PhpTry/FailedAttemptTestCase.php:
--------------------------------------------------------------------------------
1 | exception = new TestException();
16 | $this->failure = $this->createFailure($this->exception);
17 | }
18 |
19 | abstract protected function createFailure($exception);
20 |
21 | /**
22 | * @test
23 | */
24 | public function it_is_not_success()
25 | {
26 | $this->assertFalse($this->failure->isSuccess());
27 | }
28 |
29 | /**
30 | * @test
31 | */
32 | public function it_is_failure()
33 | {
34 | $this->assertTrue($this->failure->isFailure());
35 | }
36 |
37 | /**
38 | * @test
39 | * @expectedException PhpTry\TestException
40 | */
41 | public function it_throws_when_getting_the_value()
42 | {
43 | $this->failure->get();
44 | }
45 |
46 | /**
47 | * @test
48 | */
49 | public function it_returns_the_other_value_on_getOrElse()
50 | {
51 | $this->assertEquals(21, $this->failure->getOrElse(21));
52 |
53 | }
54 |
55 | /**
56 | * @test
57 | */
58 | public function it_returns_the_value_returned_by_the_callable_on_getOrCall()
59 | {
60 | $this->assertEquals(21, $this->failure->getOrCall(function(){ return 21; }));
61 | }
62 |
63 | /**
64 | * @test
65 | * @expectedException PhpTry\TestException
66 | */
67 | public function it_returns_does_not_handle_the_exception_thrown_by_the_callable_on_getOrCall()
68 | {
69 | $this->failure->getOrCall(function(){ throw new TestException(); });
70 | }
71 |
72 | /**
73 | * @test
74 | */
75 | public function it_returns_the_other_attempt_on_orElse()
76 | {
77 | $other = new Success(42);
78 | $this->assertEquals($other, $this->failure->orElse($other));
79 | }
80 |
81 | /**
82 | * @test
83 | */
84 | public function it_returns_the_result_of_the_callable_on_orElseCall()
85 | {
86 | $this->assertEquals(new Success(21), $this->failure->orElseCall(function (){ return new Success(21); }));
87 | }
88 |
89 | /**
90 | * @test
91 | */
92 | public function it_returns_Failure_if_the_result_of_the_callable_is_not_an_attempt_on_orElseCall()
93 | {
94 | $result = $this->failure->orElseCall(function (){ return 21; });
95 | $this->assertInstanceOf('PhpTry\\Failure', $result);
96 | }
97 |
98 | /**
99 | * @test
100 | */
101 | public function it_returns_Failure_if_the_callable_throws_on_orElseCall()
102 | {
103 | $result = $this->failure->orElseCall(function (){ throw new TestException(); });
104 | $this->assertInstanceOf('PhpTry\\Failure', $result);
105 | }
106 |
107 | /**
108 | * @test
109 | */
110 | public function it_does_not_return_an_element_in_foreach()
111 | {
112 | $called = false;
113 | foreach ($this->failure as $elem) {
114 | $called = true;
115 | }
116 |
117 | $this->assertFalse($called);
118 | }
119 |
120 | /**
121 | * @test
122 | * @dataProvider attemptValues
123 | */
124 | public function it_returns_the_result_of_the_callable_on_recoverWith(Attempt $attempt)
125 | {
126 | $this->assertSame($attempt, $this->failure->recoverWith(function() use ($attempt) { return $attempt; }));
127 | }
128 |
129 | public function attemptValues()
130 | {
131 | return array(
132 | array(new Success(42)),
133 | array(new Failure(new Exception())),
134 | );
135 | }
136 |
137 | /**
138 | * @test
139 | */
140 | public function it_passes_its_value_to_the_recoverWith_callable()
141 | {
142 | $value = null;
143 |
144 | $this->failure->recoverWith(function($elem) use (&$value) { $value = $elem; return new Success(21); });
145 |
146 | $this->assertSame($this->exception, $value);
147 | }
148 |
149 | /**
150 | * @test
151 | */
152 | public function it_returns_Failure_if_the_recoverWith_callable_throws()
153 | {
154 | $result = $this->failure->recoverWith(function() { throw new Exception('meh.'); });
155 |
156 | $this->assertInstanceOf('PhpTry\\Failure', $result);
157 | }
158 |
159 | /**
160 | * @test
161 | */
162 | public function it_returns_Failure_if_the_recoverWith_callable_does_not_return_an_Attempt()
163 | {
164 | $result = $this->failure->recoverWith(function() { return 21; });
165 |
166 | $this->assertInstanceOf('PhpTry\\Failure', $result);
167 | }
168 |
169 | /**
170 | * @test
171 | */
172 | public function it_returns_the_result_of_the_callable_on_recover()
173 | {
174 | $this->assertEquals(new Success(21), $this->failure->recover(function() { return 21; }));
175 | }
176 |
177 | /**
178 | * @test
179 | */
180 | public function it_passes_its_value_to_the_recover_callable()
181 | {
182 | $value = null;
183 |
184 | $this->failure->recover(function($elem) use (&$value) { $value = $elem; return 21; });
185 |
186 | $this->assertSame($this->exception, $value);
187 | }
188 |
189 | /**
190 | * @test
191 | */
192 | public function it_returns_Failure_if_the_recover_callable_throws()
193 | {
194 | $result = $this->failure->recover(function() { throw new Exception('meh.'); });
195 |
196 | $this->assertInstanceOf('PhpTry\\Failure', $result);
197 | }
198 |
199 | /**
200 | * @test
201 | */
202 | public function it_passes_its_value_to_the_onFailure_callable()
203 | {
204 | $value = null;
205 |
206 | $this->failure->onFailure(function($elem) use (&$value) { $value = $elem; });
207 |
208 | $this->assertEquals($this->exception, $value);
209 | }
210 |
211 | /**
212 | * @test
213 | */
214 | public function it_passes_does_not_call_onSuccess()
215 | {
216 | $called = false;
217 |
218 | $this->failure->onSuccess(function() use (&$called) { $called = true; });
219 |
220 | $this->assertFalse($called);
221 | }
222 |
223 | /**
224 | * @test
225 | */
226 | public function it_can_be_converted_to_a_None_option()
227 | {
228 | $this->assertEquals(\PhpOption\None::create(), $this->failure->toOption());
229 | }
230 |
231 | /**
232 | * @test
233 | */
234 | public function it_does_not_call_on_forAll()
235 | {
236 | $called = false;
237 |
238 | $this->failure->forAll(function() use (&$called) { $called = true; });
239 |
240 | $this->assertFalse($called);
241 | }
242 | }
243 |
244 | class TestException extends Exception {}
245 |
--------------------------------------------------------------------------------
/src/PhpTry/Attempt.php:
--------------------------------------------------------------------------------
1 | isSuccess() ? $this->get() : $default;
42 | }
43 |
44 | /**
45 | * Returns the value if it is Success, or the the return value of the callable otherwise.
46 | *
47 | * Note: will throw if the callable throws.
48 | *
49 | * @param callable $callable
50 | *
51 | * @return mixed
52 | */
53 | public function getOrCall($callable)
54 | {
55 | return $this->isSuccess() ? $this->get() : call_user_func($callable);
56 | }
57 |
58 | /**
59 | * Returns this Attempt if Success, or the given Attempt otherwise.
60 | *
61 | * @param Attempt $try
62 | *
63 | * @return Attempt
64 | */
65 | public function orElse(Attempt $try)
66 | {
67 | return $this->isSuccess() ? $this : $try;
68 | }
69 |
70 | /**
71 | * Returns this Attempt if Success, or the result of the callable otherwise.
72 | *
73 | * @param callable $callable Callable returning an Attempt.
74 | *
75 | * @return Attempt
76 | */
77 | public function orElseCall($callable)
78 | {
79 | if ($this->isSuccess()) {
80 | return $this;
81 | }
82 |
83 | try {
84 | $value = call_user_func($callable);
85 |
86 | if ( ! $value instanceof Attempt) {
87 | return new Failure(new UnexpectedValueException('Return value of callable should be an Attempt.'));
88 | }
89 |
90 | return $value;
91 | } catch (Exception $ex) {
92 | return new Failure($ex);
93 | }
94 | }
95 |
96 | /**
97 | * {@inheritDoc}
98 | */
99 | public function getIterator()
100 | {
101 | if ($this->isSuccess()) {
102 | return new ArrayIterator(array($this->get()));
103 | } else {
104 | return new EmptyIterator();
105 | }
106 | }
107 |
108 | /**
109 | * Its value if Success, or throws the exception if this is a Failure.
110 | *
111 | * @return mixed
112 | */
113 | abstract public function get();
114 |
115 |
116 | /**
117 | * Returns the given function applied to the value from this Success, or returns this if this is a Failure.
118 | *
119 | * Useful for calling Attempting another operation that might throw an
120 | * exception, while an already catched exception gets propagated.
121 | *
122 | * @param callable $callable Callable returning an Attempt.
123 | *
124 | * @return Attempt
125 | */
126 | abstract public function flatMap($callable);
127 |
128 | /**
129 | * Maps the given function to the value from this Success, or returns this if this is a Failure.
130 | *
131 | * @param callable $callable Callable returning a value.
132 | *
133 | * @return Attempt
134 | */
135 | abstract public function map($callable);
136 |
137 | /**
138 | * Converts this to a Failure if the predicate is not satisfied.
139 | *
140 | * @param mixed $callable Callable retuning a boolean.
141 | *
142 | * @return Attempt
143 | */
144 | abstract public function filter($callable);
145 |
146 | /**
147 | * Applies the callable if this is a Failure, otherwise returns if this is a Success.
148 | *
149 | * Note: this is like `flatMap` for the exception.
150 | *
151 | * @param callable $callable Callable taking an exception and returning an Attempt.
152 | *
153 | * @return Attempt
154 | */
155 | abstract public function recoverWith($callable);
156 |
157 | /**
158 | * Applies the callable if this is a Failure, otherwise returns if this is a Success.
159 | *
160 | * Note: this is like `map` for the exception.
161 | *
162 | * @param callable $callable Callable taking an exception and returning a value.
163 | *
164 | * @return Attempt
165 | */
166 | abstract public function recover($callable);
167 |
168 | /**
169 | * Callable called when this is a Success.
170 | *
171 | * @param mixed $callable Callable taking a value.
172 | *
173 | * @return void
174 | */
175 | abstract public function onSuccess($callable);
176 |
177 | /**
178 | * Callable called when this is a Success.
179 | *
180 | * @param mixed $callable Callable taking an exception.
181 | *
182 | * @return void
183 | */
184 | abstract public function onFailure($callable);
185 |
186 | /**
187 | * Converts the Attempt to an Option.
188 | *
189 | * Returns 'Some' if it is Success, or 'None' if it's a Failure.
190 | *
191 | * @return \PhpOption\Option
192 | */
193 | abstract public function toOption();
194 |
195 | /**
196 | * Callable called when this is a Success.
197 | *
198 | * Like `map`, but without caring about the return value of the callable.
199 | * Useful for consuming the possible value of the Attempt.
200 | *
201 | * @return Attempt The current Attempt
202 | */
203 | abstract public function forAll($callable);
204 |
205 | /**
206 | * Constructs an Attempt by calling the passed callable.
207 | *
208 | * @param callable $callable
209 | * @param array $arguments Optional arguments for the callable.
210 | *
211 | * @return Attempt
212 | */
213 | public static function call($callable, $arguments = array())
214 | {
215 | try {
216 | return new Success(call_user_func_array($callable, $arguments));
217 | } catch (Exception $e) {
218 | return new Failure($e);
219 | }
220 | }
221 |
222 | /**
223 | * Constructs a LazyAttempt by calling the passed callable.
224 | *
225 | * The callable will only be called if a method on the Attempt is called.
226 | *
227 | * @param callable $callable
228 | * @param array $arguments Optional arguments for the callable.
229 | *
230 | * @return LazyAttempt
231 | */
232 | public static function lazily($callable, $arguments = array())
233 | {
234 | return new LazyAttempt($callable, $arguments);
235 | }
236 | }
237 |
--------------------------------------------------------------------------------