11# Welcome to PhpBox
22
3- PhpBox contains exactly one class: ` Box ` .
4-
5- You can find it here: [ src/Box .php] ( src/Box .php )
3+ PhpBox contains two classes:
4+ - ` Box ` [ src/Box.php ] ( src/Box.php )
5+ - ` TryBox ` [ src/TryBox .php] ( src/TryBox .php )
66
77# Installation
8- Currently, there is no composer package.
9-
10- Simply copy the ` Box.php ` file to your project and adjust its namespace if needed.
8+ A composer package is in the works, but I recommend just copying the files into your project for now.
119
1210# Requirements
1311The only hard requirement is PHP 8.0 or higher.
@@ -23,25 +21,20 @@ You put any value into a box.
2321
2422Then you can chain map(), dump() and assert() calls on it.
2523
26- To get the value out of the box, call unbox () or get().
24+ To get the value out of the box, call value () or get().
2725
28- ## Note
29- In an earlier version:
30- - map() was called pipe()
31- - get() was called pull()
32-
33- ## Examples
26+ ## Box Examples
3427
3528``` php
3629$value = Box::of(5)
3730 ->map(fn($value) => $value + 1)
3831 ->map(fn($value) => $value * 2)
39- ->unbox ();
32+ ->value ();
4033
4134echo $value; // 12
4235```
4336
44- Use get() to combine map() and unbox () in one call:
37+ Use get() to combine map() and value () in one call:
4538``` php
4639$value = Box::of(5)
4740 ->map(fn($value) => $value + 1)
@@ -57,7 +50,7 @@ $isEven = fn($value) => $value % 2 === 0;
5750$value = Box::of(5)
5851 ->map(fn($it) => $it + 1)->assert(6)
5952 ->map(fn($it) => $it * 2)->assert($isEven)->dump()
60- ->unbox ();
53+ ->value ();
6154
6255echo $value; // 12
6356```
@@ -84,28 +77,85 @@ $user = Box::of($inputEmail)
8477 ->get(fn($it) => $userRepository->create(['email' => $it]));
8578```
8679
87- Using flatMap , you can compose presets of operations on boxes :
80+ Using mod() , you can compose presets of operations:
8881``` php
8982/** @throws LogicException */
9083function assertEmail(Box $box): Box
9184{
9285 return $box
93- ->assert(fn($x) => is_string($x), 'Not a string')
86+ ->assert(fn(mixed $x) => is_string($x), 'Not a string')
9487 ->assert(fn(string $x) => strlen($x) > 0, 'Too short')
9588 ->assert(fn(string $x) => strlen($x) < 256, 'Too long')
9689 ->assert(fn(string $x) => filter_var($x, FILTER_VALIDATE_EMAIL), 'Not an email');
9790}
9891
99- $validEmail = Box::of('')->flatMap(assertEmail(...))->unbox ();
100- // throws LogicException: "Value is too short"
92+ $validEmail = Box::of('')->flatMap(assertEmail(...))->value ();
93+ // throws LogicException: "Too short"
10194
10295$user = Box::of('
[email protected] ')
103- ->flatMap (fn($box) => assertEmail($box))
96+ ->mod (fn($box) => assertEmail($box))
10497 ->get(fn($email) => $userRepository->create(['email' => $email]));
10598```
106- Note, in this example we still have 4 separate assertions and error messages.
107- Using flatMap is the key here. Unlike map(), which transforms the value inside the Box,
108- flatMap() transforms the Box itself. This allows us to compose behavior in a more functional way.
99+
100+ ## TryBox Examples
101+
102+ TryBox works just like Box, except that it catches any and all errors instead of blowing up directly.
103+ Its value() method has a more complicated return type, which is either T or Throwable.
104+ - advantage: you can run a chain without risk of blowing up (subsequent map() calls will be skipped),
105+ and then decide what to do with the error at the end.
106+ - disadvantage: you have to handle the error case even if you're sure it will never happen.
107+
108+ Let's define a realistic function that sometimes throws an error (like a database call or an API request). In this
109+ case it either returns whatever was passed in or throws a RuntimeException at random.
110+ ``` php
111+ /**
112+ * @template T
113+ * @param T $value
114+ * @return T
115+ *
116+ * @throws RuntimeException
117+ */
118+ function roulette($value)
119+ {
120+ if (random_int(0, 1) === 1) {
121+ throw new RuntimeException('boo');
122+ }
123+
124+ return $value;
125+ }
126+ ```
127+
128+ Let's use this dangerous function in a TryBox chain:
129+ ``` php
130+ $result = TryBox::of(5)
131+ ->map(fn($value) => roulette($value)) // TryBox stores the exception
132+ ->map(fn($value) => $value * 2)
133+ ->map(fn($value) => $value + 1)
134+ ->value();
135+
136+ // type signature is int|Throwable, so we have to handle the error case
137+ if ($result instanceof Throwable) {
138+ echo $result->getMessage(); // boo
139+ return;
140+ }
141+
142+ // if we reach this line, $result is guaranteed to be an int
143+ $calc = $result + 1;
144+ echo $calc;
145+ ```
146+
147+ If you are sure the error case will never happen, or simply don't care, you can use rip() instead of value():
148+ ` rip() ` will either throw the stored error (if any) or return the value of type T.
149+ ``` php
150+ $result = TryBox::of(5)
151+ ->map(fn($value) => roulette($value))
152+ ->map(fn($value) => $value * 2)
153+ ->map(fn($value) => $value + 1)
154+ ->rip(); // The original runtime exception will be thrown here, if present
155+
156+ // if we reach this line, $result is guaranteed to be an int
157+ $calc = $result + 1;
158+ ```
109159
110160# Type Safety
111161Thanks to meticulously crafted PHPDoc annotations, this class is type safe if you use PHPStan for static analysis.
@@ -137,8 +187,6 @@ The `@template T` annotation tells PHPStan that the class is generic and that th
137187In the constructor, we use the $value parameter with the type T,
138188and the type of the Box is automatically reverse engineered from the input.
139189
140-
141-
142190Examples:
143191- ` Box::of(5) ` will be of type ` Box<int> `
144192- ` Box::of('hello') ` will be of type ` Box<string> `
@@ -168,10 +216,8 @@ the type of U, and the resulting `Box<U>` is inferred from the return type of th
168216
169217This mechanism is incredibly powerful at preventing you from writing bad code. And again, a totally normal feature
170218in other languages like Java, Scala, Kotlin, Haskell, Rust, F#, C#, Go, Swift, TypeScript.
171- Please pick one of these and learn it.
172-
173219``` php
174- $value = Box::of('Hello World') // string
220+ $value = Box::of('Hello World') // string
175221 ->map(fn($value) => strtoupper($value)) // string
176222 ->map(fn($value) => str_replace('WORLD', 'PHP', $value)) // string
177223 ->map(fn($value) => str_split($value)) // array<string >
0 commit comments