Skip to content

Commit d586c5b

Browse files
authored
Merge pull request #9 from ghostwriter/feature/refactor-generics
feature/refactor generics
2 parents 5ae268e + a7c4ea2 commit d586c5b

10 files changed

+100
-107
lines changed

README.md

+18-10
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,30 @@ composer require ghostwriter/option
1919
## Usage
2020

2121
```php
22-
use Ghostwriter\Option\{Some, None, OptionInterface};
22+
use Ghostwriter\Option\Exception\NullPointerException;
23+
use Ghostwriter\Option\None;
24+
use Ghostwriter\Option\Some;
2325

24-
// basic setting and getting of values
2526
$greeting = Some::create('Hello World!');
26-
echo $greeting->unwrap(); // echos 'Hello World!'
27+
echo $greeting->unwrap(); // echos: 'Hello World!'
2728

2829
$name = None::create();
29-
echo $name->unwrap(); // throws an OptionException
30-
echo $name->unwrapOr('Unknown'); // echos 'Unknown'
30+
echo $name->unwrap(); // throws: NullPointerException
31+
echo $name->unwrapOr('Default Value'); // echos: 'Default Value'
32+
33+
None::create(); // returns: None
34+
Some::of(null); // returns: None
35+
Some::create(null); // throws: NullPointerException
36+
37+
--- Example
3138

3239
function divide(int $x, int $y): OptionInterface
3340
{
34-
if ($y === 0) {
35-
return Nome::create();
36-
}
37-
return Some::create($x / $y);
41+
if ($y === 0) {
42+
return Nome::create();
43+
}
44+
45+
return Some::create($x / $y);
3846
}
3947

4048
divide(1, 0); // None
@@ -56,7 +64,7 @@ Please see [CHANGELOG.md](./CHANGELOG.md) for more information what has changed
5664
If you discover any security related issues, please email `nathanael.esayeas@protonmail.com` instead of using the issue tracker.
5765

5866
## Sponsors
59-
[[Become a GitHub Sponsor](https://github.com/sponsors/ghostwriter)]
67+
[[`Become a GitHub Sponsor`](https://github.com/sponsors/ghostwriter)]
6068

6169
## Credits
6270

composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@
3737
"phpbench/phpbench": "^1.2.6",
3838
"phpunit/phpunit": "^9.5.21",
3939
"psalm/plugin-phpunit": "^0.17.0",
40-
"rector/rector": "^0.13.9",
40+
"rector/rector": "^0.13.10",
4141
"symplify/easy-coding-standard": "^11.0.9",
42-
"vimeo/psalm": "^4.25.0"
42+
"vimeo/psalm": "^4.26"
4343
},
4444
"minimum-stability": "stable",
4545
"prefer-stable": true,

composer.lock

+13-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

infection.json.dist

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@
1717
"text": "php:\/\/stderr",
1818
"github": true
1919
},
20-
"minMsi": 0,
21-
"minCoveredMsi": 0
20+
"minMsi": 100,
21+
"minCoveredMsi": 100
2222
}

src/AbstractOption.php

+4-21
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use Ghostwriter\Option\Contract\NoneInterface;
88
use Ghostwriter\Option\Contract\OptionInterface;
99
use Ghostwriter\Option\Contract\SomeInterface;
10-
use Ghostwriter\Option\Exception\InvalidReturnTypeException;
1110
use Ghostwriter\Option\Exception\NullPointerException;
1211
use Throwable;
1312
use Traversable;
@@ -42,12 +41,7 @@ public function andThen(callable $function): OptionInterface
4241
return $this;
4342
}
4443

45-
$result = $function($this->value);
46-
if ($result instanceof OptionInterface) {
47-
return $result;
48-
}
49-
50-
throw new InvalidReturnTypeException();
44+
return self::of($function($this->value));
5145
}
5246

5347
public function contains(mixed $value): bool
@@ -78,13 +72,7 @@ public function filter(callable $function): OptionInterface
7872

7973
public function flatten(): OptionInterface
8074
{
81-
if ($this instanceof NoneInterface) {
82-
return $this;
83-
}
84-
85-
$unwrapped = $this->value;
86-
87-
return $unwrapped instanceof SomeInterface ? $unwrapped : $this;
75+
return $this->andThen(fn (mixed $value) => $value instanceof SomeInterface ? $value : $this);
8876
}
8977

9078
public function getIterator(): Traversable
@@ -110,7 +98,7 @@ public function map(callable $function): OptionInterface
11098
return $this;
11199
}
112100

113-
return Some::create($function($this->value));
101+
return self::of($function($this->value));
114102
}
115103

116104
public function mapOr(callable $function, mixed $fallback): mixed
@@ -159,12 +147,7 @@ public function orElse(callable $function): OptionInterface
159147
return $this;
160148
}
161149

162-
$result = $function();
163-
if ($result instanceof OptionInterface) {
164-
return $result;
165-
}
166-
167-
throw new InvalidReturnTypeException();
150+
return self::of($function());
168151
}
169152

170153
public function unwrap(): mixed

src/Contract/OptionInterface.php

+27-11
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ public function and(self $option): self;
2323
/**
2424
* Returns None if the option is None, otherwise calls $function with the wrapped value and returns the result.
2525
*
26-
* @param callable(TValue): OptionInterface $function
26+
* @template TAndThen
27+
*
28+
* @param callable(TValue):TAndThen $function
29+
*
30+
* @return self<TAndThen|TValue>
2731
*/
2832
public function andThen(callable $function): self;
2933

@@ -38,14 +42,18 @@ public function contains(mixed $value): bool;
3842
* Returns the contained Some value, consuming the self value.
3943
*
4044
* @throws Throwable if the value is a None with a custom $throwable provided
45+
*
46+
* @return TValue
4147
*/
4248
public function expect(Throwable $throwable): mixed;
4349

4450
/**
4551
* Returns None if the option is None, otherwise calls $function with the wrapped value and returns: Some(TValue) if
4652
* $function returns true (where TValue is the wrapped value), and None if $function returns false.
4753
*
48-
* @param callable(TValue): bool $function
54+
* @param callable(TValue):bool $function
55+
*
56+
* @return self<TValue>
4957
*/
5058
public function filter(callable $function): self;
5159

@@ -71,31 +79,37 @@ public function isSome(): bool;
7179
/**
7280
* Maps a Some<TValue> to Some<TValue> by applying the callable $function to the contained value.
7381
*
74-
* @param callable(TValue):TValue $function
82+
* @template TMap
7583
*
76-
* @return self<TValue>
84+
* @param callable(TValue):TMap $function
85+
*
86+
* @return self<TMap>
7787
*/
7888
public function map(callable $function): self;
7989

8090
/**
8191
* Applies a function to the contained value (if any), or returns the provided default (if not).
8292
*
83-
* @template TFallbackValue
93+
* @template TFunction
94+
* @template TFallback
8495
*
85-
* @param callable(TValue): TFallbackValue $function
86-
* @param TFallbackValue $fallback
96+
* @param callable(TValue): TFunction $function
97+
* @param TFallback $fallback
8798
*
88-
* @return TFallbackValue
99+
* @return TFallback|TFunction
89100
*/
90101
public function mapOr(callable $function, mixed $fallback): mixed;
91102

92103
/**
93104
* Applies a function to the contained value (if any), or computes a default (if not).
94105
*
95-
* @template TFallbackValue
106+
* @template TFunction
107+
* @template TFallback
108+
*
109+
* @param callable(TValue):TFunction $function
110+
* @param callable():TFallback $fallback
96111
*
97-
* @param callable(mixed): mixed $function
98-
* @param callable(): mixed $fallback
112+
* @return TFallback|TFunction
99113
*/
100114
public function mapOrElse(callable $function, callable $fallback): mixed;
101115

@@ -107,6 +121,8 @@ public function mapOrElse(callable $function, callable $fallback): mixed;
107121
* @template TNullableValue
108122
*
109123
* @param TNullableValue $value the actual value
124+
*
125+
* @return self<TNullableValue|TValue>
110126
*/
111127
public static function of(mixed $value): self;
112128

src/Exception/InvalidReturnTypeException.php

-12
This file was deleted.

tests/Unit/AbstractOptionTest.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Ghostwriter\Option\AbstractOption;
88
use Ghostwriter\Option\Contract\NoneInterface;
9+
use Ghostwriter\Option\Contract\OptionInterface;
910
use Ghostwriter\Option\Contract\SomeInterface;
1011
use Ghostwriter\Option\None;
1112
use Ghostwriter\Option\Some;
@@ -49,10 +50,19 @@ public function ofDataProvider(): Traversable
4950
*
5051
* @dataProvider ofDataProvider
5152
*
53+
* @template TValue
54+
*
5255
* @param class-string $expected
56+
* @param TValue $value
5357
*/
5458
public function testOf(string $expected, mixed $value): void
5559
{
56-
self::assertInstanceOf($expected, AbstractOption::of($value));
60+
$option = AbstractOption::of($value);
61+
62+
if ($value instanceof OptionInterface) {
63+
self::assertSame($value, $option);
64+
}
65+
66+
self::assertInstanceOf($expected, $option);
5767
}
5868
}

0 commit comments

Comments
 (0)