|  | 
|  | 1 | +# PHP Optional | 
|  | 2 | + | 
|  | 3 | +Inspired by Java's [Optional](https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/share/classes/java/util/function/Predicate.java) class, this package aims to provide a comprehensive API | 
|  | 4 | +for optional field values. | 
|  | 5 | + | 
|  | 6 | +## Examples | 
|  | 7 | + | 
|  | 8 | +You can use the `Optional` class in a variety of ways, see below for a few examples: | 
|  | 9 | + | 
|  | 10 | +### Updating a user's profile | 
|  | 11 | + | 
|  | 12 | +The below class indicates who this package may be used to update a users profile information. All fields are optional, because | 
|  | 13 | +our user may not want to update all fields. | 
|  | 14 | + | 
|  | 15 | +```php | 
|  | 16 | +<?php | 
|  | 17 | + | 
|  | 18 | +namespace App\DataObjects; | 
|  | 19 | + | 
|  | 20 | +use JoelButcher\PhpOptional\Optional; | 
|  | 21 | +use Psl\Type; | 
|  | 22 | +use Webmozart\Assert\Assert; | 
|  | 23 | + | 
|  | 24 | +class UpdateProfile | 
|  | 25 | +{ | 
|  | 26 | +    public function __construct( | 
|  | 27 | +        private readonly int $id | 
|  | 28 | +        private readonly Optional $name | 
|  | 29 | +        private readonly Optional $email | 
|  | 30 | +        private readonly Options $bio | 
|  | 31 | +    ) { | 
|  | 32 | +        // | 
|  | 33 | +    } | 
|  | 34 | + | 
|  | 35 | +    public static function fromFormRequest(int $id UpdateProfileRequest $request): self | 
|  | 36 | +    { | 
|  | 37 | +        return new self( | 
|  | 38 | +            id: $id, | 
|  | 39 | +            name: Optional::forNullable($request->get('name')), | 
|  | 40 | +            email: Optional::forNullable($request->get('email')), | 
|  | 41 | +            bio: Optional::forNullable($request->get('bio')), | 
|  | 42 | +        ); | 
|  | 43 | +    } | 
|  | 44 | + | 
|  | 45 | +    public static function fromArray(array $data): self | 
|  | 46 | +    { | 
|  | 47 | +        Assert::keyExists($post, 'id'); | 
|  | 48 | +        Assert::positiveInteger($post['id']); | 
|  | 49 | + | 
|  | 50 | + | 
|  | 51 | +        return new self( | 
|  | 52 | +            id: $data['id'], | 
|  | 53 | +            name: Optional::forOptionalArrayKey($data, 'name', Type\non_empty_string()), | 
|  | 54 | +            email: Optional::forOptionalArrayKey($data, 'email', Type\non_empty_string()), | 
|  | 55 | +            bio: Optional::forOptionalArrayKey($data, 'bio', Type\non_empty_string()), | 
|  | 56 | +        ); | 
|  | 57 | +    } | 
|  | 58 | + | 
|  | 59 | +    public function handle(UserRepository $users): void | 
|  | 60 | +    { | 
|  | 61 | +        $user = $users->findOneById($this->id); | 
|  | 62 | + | 
|  | 63 | +        // These callbacks are only called if the value for each optional field is present. | 
|  | 64 | +        $this->name->apply(fn (string $name) => $user->updateName($name)); | 
|  | 65 | +        $this->email->apply(fn (string $email) => $user->updateEmail($email)); | 
|  | 66 | +        $this->bio->apply(fn (string $bio) => $user->updateBio($bio)); | 
|  | 67 | + | 
|  | 68 | +        $users->save($user); | 
|  | 69 | +    } | 
|  | 70 | +} | 
|  | 71 | +``` | 
|  | 72 | + | 
|  | 73 | +Noticed the `\Psl\Type\non_empty_string()` call? That's an abstraction coming from `azjezz/psl`, which allows for having a type declared both at runtime and at static analysis level. We use it to parse inputs into valid values, or to produce crashes, if something is malformed. | 
|  | 74 | + | 
|  | 75 | +The `azjezz/psl`, `Psl\Type` tooling gives us both type safety and runtime validation by implicitly validating our values: | 
|  | 76 | + | 
|  | 77 | +```php | 
|  | 78 | +OptionalField::forPossiblyMissingArrayKey( | 
|  | 79 | +    ['foo' => 'bar'], | 
|  | 80 | +    'foo', | 
|  | 81 | +    \Psl\Type\positive_int() | 
|  | 82 | +); // crashes: `foo` does not contain a `positive-int`! | 
|  | 83 | +``` | 
0 commit comments