Last active
February 24, 2024 10:10
-
-
Save ollieread/0069ef279ad0d6c1475b5ba4ea66ab47 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** @noinspection UnknownInspectionInspection */ | |
declare(strict_types=1); | |
namespace Smpl\Logic; | |
use Smpl\Logic\Contracts\Consumer; | |
use Smpl\Logic\Contracts\Supplier; | |
use Smpl\Logic\Exceptions\OptionalException; | |
use Throwable; | |
/** | |
* Optional | |
* | |
* Optionals are containers of optional values, and serve as an alternative | |
* to returning a nullable value. | |
* | |
* @template ValType of mixed | |
*/ | |
final class Optional | |
{ | |
/** | |
* Create an instance from the value | |
* | |
* This method creates an optional instance from the provided value if it | |
* isn't null, otherwise it returns an empty optional. | |
* | |
* @template OfType of mixed | |
* | |
* @param OfType|null $value | |
* | |
* @return self<OfType>|self<void> | |
*/ | |
public static function of(mixed $value): self | |
{ | |
if ($value !== null) { | |
return new self($value); | |
} | |
return self::empty(); | |
} | |
/** | |
* Create an instance from the nullable value | |
* | |
* This method creates an optional instance from the provided value, | |
* regardless of whether it's null. | |
* | |
* @template OfType of mixed | |
* | |
* @param OfType|null $value | |
* | |
* @return self<OfType|null> | |
*/ | |
public static function ofNullable(mixed $value): self | |
{ | |
return new self($value); | |
} | |
/** | |
* Create an empty instance | |
* | |
* This method returns an empty 'void' optional instance. | |
* | |
* @return self<void> | |
* | |
* @psalm-suppress MixedReturnTypeCoercion | |
*/ | |
public static function empty(): self | |
{ | |
return new self(); | |
} | |
/** | |
* The optional value | |
* | |
* @var ValType | |
* | |
* @psalm-suppress PropertyNotSetInConstructor | |
*/ | |
private mixed $value; | |
/** | |
* The empty state | |
* | |
* @var bool | |
*/ | |
private bool $empty = false; | |
/** | |
* Create a new instance of the optional class | |
* | |
* @throws \Smpl\Logic\Exceptions\OptionalException If called with more than one parameter | |
*/ | |
private function __construct() | |
{ | |
if (func_num_args() === 1) { | |
/** @psalm-suppress MixedAssignment */ | |
$this->value = func_get_arg(0); | |
$this->empty = false; | |
} else if (func_num_args() > 1) { | |
throw OptionalException::toManyParameters(); | |
} else { | |
$this->empty = true; | |
} | |
} | |
/** | |
* Check if the optional is empty | |
* | |
* Empty optionals were instantiated with no value, not even a null value. | |
* Optionals created from null values are not considered empty. | |
* | |
* @return bool | |
*/ | |
public function isEmpty(): bool | |
{ | |
return $this->empty; | |
} | |
/** | |
* Check if a value is present | |
* | |
* This method checks that the optional is not empty, and that the value | |
* is not null. | |
* Empty optionals, or optionals with a null value will return false here. | |
* | |
* @return bool | |
*/ | |
public function isPresent(): bool | |
{ | |
return ! $this->isEmpty() && isset($this->value); | |
} | |
/** | |
* Get the optional value | |
* | |
* @return ValType | |
* | |
* @throws \RuntimeException If there is no value | |
*/ | |
public function get(): mixed | |
{ | |
if (! $this->isEmpty()) { | |
return $this->value; | |
} | |
throw OptionalException::noValue(); | |
} | |
/** | |
* If a value is present, use a consumer | |
* | |
* This method uses the provided {@see \Smpl\Logic\Contracts\Consumer with | |
* its value if a value is present, otherwise it does nothing. | |
* | |
* @param \Smpl\Logic\Contracts\Consumer<ValType> $consumer | |
* | |
* @return void | |
*/ | |
public function ifPresent(Consumer $consumer): void | |
{ | |
if ($this->isPresent()) { | |
$consumer->consume($this->get()); | |
} | |
} | |
/** | |
* Get the optional value with the provided fallback | |
* | |
* This method returns the current value if there is one, or the provided | |
* fallback value if there isn't one. | |
* | |
* @template OtherType of mixed | |
* | |
* @param OtherType|ValType $fallback | |
* | |
* @return OtherType|ValType | |
*/ | |
public function orElse(mixed $fallback): mixed | |
{ | |
return $this->isPresent() ? $this->get() : $fallback; | |
} | |
/** | |
* Get the optional value or use the supplier to get a fallback | |
* | |
* This method returns the current value if there is one, or the value | |
* returned by the provided {@see \Smpl\Logic\Contracts\Supplier}. | |
* | |
* @template OtherType of mixed | |
* | |
* @param \Smpl\Logic\Contracts\Supplier<OtherType|ValType> $supplier | |
* | |
* @return OtherType|ValType | |
*/ | |
public function orElseGet(Supplier $supplier): mixed | |
{ | |
return $this->isPresent() ? $this->get() : $supplier->get(); | |
} | |
/** | |
* Get the optional value or throw an exception | |
* | |
* This method returns the current value if there is one, or throws an | |
* exception either provided directly, or through a | |
* {@see \Smpl\Logic\Contracts\Supplier}. | |
* | |
* @template ThrowType of \Throwable | |
* | |
* @param ThrowType|\Smpl\Logic\Contracts\Supplier<ThrowType> $throw | |
* | |
* @return ValType | |
* | |
* @throws ThrowType&\Throwable If no value is present | |
* | |
* @psalm-suppress UndefinedDocblockClass | |
* | |
* @noinspection PhpDocSignatureInspection | |
*/ | |
public function orElseThrow(Throwable|Supplier $throw): mixed | |
{ | |
if ($this->isPresent()) { | |
return $this->get(); | |
} | |
if ($throw instanceof Supplier) { | |
/** @var \Smpl\Logic\Contracts\Supplier<ThrowType> $throw */ | |
throw $throw->get(); | |
} | |
throw $throw; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment