Init
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Option;
|
||||
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @extends Option<T>
|
||||
*/
|
||||
final class LazyOption extends Option
|
||||
{
|
||||
/** @var callable(mixed...):(Option<T>) */
|
||||
private $callback;
|
||||
|
||||
/** @var array<int, mixed> */
|
||||
private $arguments;
|
||||
|
||||
/** @var Option<T>|null */
|
||||
private $option;
|
||||
|
||||
/**
|
||||
* @template S
|
||||
* @param callable(mixed...):(Option<S>) $callback
|
||||
* @param array<int, mixed> $arguments
|
||||
*
|
||||
* @return LazyOption<S>
|
||||
*/
|
||||
public static function create($callback, array $arguments = []): self
|
||||
{
|
||||
return new self($callback, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(mixed...):(Option<T>) $callback
|
||||
* @param array<int, mixed> $arguments
|
||||
*/
|
||||
public function __construct($callback, array $arguments = [])
|
||||
{
|
||||
if (!is_callable($callback)) {
|
||||
throw new \InvalidArgumentException("Invalid callback given");
|
||||
}
|
||||
|
||||
$this->callback = $callback;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
public function isDefined(): bool
|
||||
{
|
||||
return $this->option()->isDefined();
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->option()->isEmpty();
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
return $this->option()->get();
|
||||
}
|
||||
|
||||
public function getOrElse($default)
|
||||
{
|
||||
return $this->option()->getOrElse($default);
|
||||
}
|
||||
|
||||
public function getOrCall($callable)
|
||||
{
|
||||
return $this->option()->getOrCall($callable);
|
||||
}
|
||||
|
||||
public function getOrThrow(\Exception $ex)
|
||||
{
|
||||
return $this->option()->getOrThrow($ex);
|
||||
}
|
||||
|
||||
public function orElse(Option $else)
|
||||
{
|
||||
return $this->option()->orElse($else);
|
||||
}
|
||||
|
||||
public function ifDefined($callable)
|
||||
{
|
||||
$this->option()->forAll($callable);
|
||||
}
|
||||
|
||||
public function forAll($callable)
|
||||
{
|
||||
return $this->option()->forAll($callable);
|
||||
}
|
||||
|
||||
public function map($callable)
|
||||
{
|
||||
return $this->option()->map($callable);
|
||||
}
|
||||
|
||||
public function flatMap($callable)
|
||||
{
|
||||
return $this->option()->flatMap($callable);
|
||||
}
|
||||
|
||||
public function filter($callable)
|
||||
{
|
||||
return $this->option()->filter($callable);
|
||||
}
|
||||
|
||||
public function filterNot($callable)
|
||||
{
|
||||
return $this->option()->filterNot($callable);
|
||||
}
|
||||
|
||||
public function select($value)
|
||||
{
|
||||
return $this->option()->select($value);
|
||||
}
|
||||
|
||||
public function reject($value)
|
||||
{
|
||||
return $this->option()->reject($value);
|
||||
}
|
||||
|
||||
/** @return Traversable<T> */
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return $this->option()->getIterator();
|
||||
}
|
||||
|
||||
public function foldLeft($initialValue, $callable)
|
||||
{
|
||||
return $this->option()->foldLeft($initialValue, $callable);
|
||||
}
|
||||
|
||||
public function foldRight($initialValue, $callable)
|
||||
{
|
||||
return $this->option()->foldRight($initialValue, $callable);
|
||||
}
|
||||
|
||||
/** @return Option<T> */
|
||||
private function option(): Option
|
||||
{
|
||||
if (null === $this->option) {
|
||||
/** @var mixed */
|
||||
$option = call_user_func_array($this->callback, $this->arguments);
|
||||
if ($option instanceof Option) {
|
||||
$this->option = $option;
|
||||
} else {
|
||||
throw new \RuntimeException(
|
||||
sprintf("Expected instance of %s", Option::class),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->option;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Option;
|
||||
|
||||
use EmptyIterator;
|
||||
|
||||
/**
|
||||
* @extends Option<mixed>
|
||||
*/
|
||||
final class None extends Option
|
||||
{
|
||||
/** @var None|null */
|
||||
private static $instance;
|
||||
|
||||
/** @return None */
|
||||
public static function create(): self
|
||||
{
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
throw new \RuntimeException("None has no value.");
|
||||
}
|
||||
|
||||
public function getOrCall($callable)
|
||||
{
|
||||
return $callable();
|
||||
}
|
||||
|
||||
public function getOrElse($default)
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
|
||||
public function getOrThrow(\Exception $ex)
|
||||
{
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isDefined(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function orElse(Option $else)
|
||||
{
|
||||
return $else;
|
||||
}
|
||||
|
||||
public function ifDefined($callable)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
public function forAll($callable)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function map($callable)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function flatMap($callable)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function filter($callable)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function filterNot($callable)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function select($value)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function reject($value)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIterator(): EmptyIterator
|
||||
{
|
||||
return new EmptyIterator();
|
||||
}
|
||||
|
||||
public function foldLeft($initialValue, $callable)
|
||||
{
|
||||
return $initialValue;
|
||||
}
|
||||
|
||||
public function foldRight($initialValue, $callable)
|
||||
{
|
||||
return $initialValue;
|
||||
}
|
||||
|
||||
private function __construct() {}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Option;
|
||||
|
||||
use ArrayAccess;
|
||||
use IteratorAggregate;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @implements IteratorAggregate<T>
|
||||
*/
|
||||
abstract class Option implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @template S
|
||||
*
|
||||
* @param S $value
|
||||
* @param S $noneValue
|
||||
*
|
||||
* @return Option<S>
|
||||
*/
|
||||
public static function fromValue($value, $noneValue = null)
|
||||
{
|
||||
if ($value === $noneValue) {
|
||||
return None::create();
|
||||
}
|
||||
|
||||
return new Some($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template S
|
||||
*
|
||||
* @param array<string|int,S>|ArrayAccess<string|int,S>|null $array
|
||||
* @param string|int|null $key
|
||||
*
|
||||
* @return Option<S>
|
||||
*/
|
||||
public static function fromArraysValue($array, $key)
|
||||
{
|
||||
if (
|
||||
$key === null ||
|
||||
!(is_array($array) || $array instanceof ArrayAccess) ||
|
||||
!isset($array[$key])
|
||||
) {
|
||||
return None::create();
|
||||
}
|
||||
|
||||
return new Some($array[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template S
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param array $arguments
|
||||
* @param S $noneValue
|
||||
*
|
||||
* @return LazyOption<S>
|
||||
*/
|
||||
public static function fromReturn(
|
||||
$callback,
|
||||
array $arguments = [],
|
||||
$noneValue = null,
|
||||
) {
|
||||
return new LazyOption(static function () use (
|
||||
$callback,
|
||||
$arguments,
|
||||
$noneValue,
|
||||
) {
|
||||
/** @var mixed */
|
||||
$return = call_user_func_array($callback, $arguments);
|
||||
|
||||
if ($return === $noneValue) {
|
||||
return None::create();
|
||||
}
|
||||
|
||||
return new Some($return);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @template S
|
||||
*
|
||||
* @param Option<S>|callable|S $value
|
||||
* @param S $noneValue
|
||||
*
|
||||
* @return Option<S>|LazyOption<S>
|
||||
*/
|
||||
public static function ensure($value, $noneValue = null)
|
||||
{
|
||||
if ($value instanceof self) {
|
||||
return $value;
|
||||
} elseif (is_callable($value)) {
|
||||
return new LazyOption(static function () use ($value, $noneValue) {
|
||||
/** @var mixed */
|
||||
$return = $value();
|
||||
|
||||
if ($return instanceof self) {
|
||||
return $return;
|
||||
} else {
|
||||
return self::fromValue($return, $noneValue);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return self::fromValue($value, $noneValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template S
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param mixed $noneValue
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function lift($callback, $noneValue = null)
|
||||
{
|
||||
return static function () use ($callback, $noneValue) {
|
||||
/** @var array<int, mixed> */
|
||||
$args = func_get_args();
|
||||
|
||||
$reduced_args = array_reduce(
|
||||
$args,
|
||||
/** @param bool $status */
|
||||
static function ($status, self $o) {
|
||||
return $o->isEmpty() ? true : $status;
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
if ($reduced_args) {
|
||||
return None::create();
|
||||
}
|
||||
|
||||
$args = array_map(static function (self $o) {
|
||||
return $o->get();
|
||||
}, $args);
|
||||
|
||||
return self::ensure(
|
||||
call_user_func_array($callback, $args),
|
||||
$noneValue,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/** @return T */
|
||||
abstract public function get();
|
||||
|
||||
/**
|
||||
* @template S
|
||||
* @param S $default
|
||||
* @return T|S
|
||||
*/
|
||||
abstract public function getOrElse($default);
|
||||
|
||||
/**
|
||||
* @template S
|
||||
* @param callable():S $callable
|
||||
* @return T|S
|
||||
*/
|
||||
abstract public function getOrCall($callable);
|
||||
|
||||
/** @return T */
|
||||
abstract public function getOrThrow(\Exception $ex);
|
||||
|
||||
abstract public function isEmpty(): bool;
|
||||
|
||||
abstract public function isDefined(): bool;
|
||||
|
||||
/**
|
||||
* @param Option<T> $else
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function orElse(self $else);
|
||||
|
||||
/** @deprecated Use forAll() instead. */
|
||||
abstract public function ifDefined($callable);
|
||||
|
||||
/**
|
||||
* @param callable(T):mixed $callable
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function forAll($callable);
|
||||
|
||||
/**
|
||||
* @template S
|
||||
* @param callable(T):S $callable
|
||||
* @return Option<S>
|
||||
*/
|
||||
abstract public function map($callable);
|
||||
|
||||
/**
|
||||
* @template S
|
||||
* @param callable(T):Option<S> $callable
|
||||
* @return Option<S>
|
||||
*/
|
||||
abstract public function flatMap($callable);
|
||||
|
||||
/**
|
||||
* @param callable(T):bool $callable
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function filter($callable);
|
||||
|
||||
/**
|
||||
* @param callable(T):bool $callable
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function filterNot($callable);
|
||||
|
||||
/**
|
||||
* @param T $value
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function select($value);
|
||||
|
||||
/**
|
||||
* @param T $value
|
||||
* @return Option<T>
|
||||
*/
|
||||
abstract public function reject($value);
|
||||
|
||||
/**
|
||||
* @template S
|
||||
* @param S $initialValue
|
||||
* @param callable(S, T):S $callable
|
||||
* @return S
|
||||
*/
|
||||
abstract public function foldLeft($initialValue, $callable);
|
||||
|
||||
/**
|
||||
* @template S
|
||||
* @param S $initialValue
|
||||
* @param callable(T, S):S $callable
|
||||
* @return S
|
||||
*/
|
||||
abstract public function foldRight($initialValue, $callable);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Option;
|
||||
|
||||
use ArrayIterator;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @extends Option<T>
|
||||
*/
|
||||
final class Some extends Option
|
||||
{
|
||||
/** @var T */
|
||||
private $value;
|
||||
|
||||
/** @param T $value */
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template U
|
||||
* @param U $value
|
||||
* @return Some<U>
|
||||
*/
|
||||
public static function create($value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
public function isDefined(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getOrElse($default)
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getOrCall($callable)
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getOrThrow(\Exception $ex)
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function orElse(Option $else): Some
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function ifDefined($callable): void
|
||||
{
|
||||
$this->forAll($callable);
|
||||
}
|
||||
|
||||
public function forAll($callable): Some
|
||||
{
|
||||
$callable($this->value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function map($callable): Some
|
||||
{
|
||||
return new self($callable($this->value));
|
||||
}
|
||||
|
||||
public function flatMap($callable): Option
|
||||
{
|
||||
/** @var mixed */
|
||||
$rs = $callable($this->value);
|
||||
if (!$rs instanceof Option) {
|
||||
throw new \RuntimeException(
|
||||
"Callables passed to flatMap() must return an Option. Maybe you should use map() instead?",
|
||||
);
|
||||
}
|
||||
|
||||
return $rs;
|
||||
}
|
||||
|
||||
public function filter($callable)
|
||||
{
|
||||
if (true === $callable($this->value)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return None::create();
|
||||
}
|
||||
|
||||
public function filterNot($callable)
|
||||
{
|
||||
if (false === $callable($this->value)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return None::create();
|
||||
}
|
||||
|
||||
public function select($value)
|
||||
{
|
||||
if ($this->value === $value) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return None::create();
|
||||
}
|
||||
|
||||
public function reject($value)
|
||||
{
|
||||
if ($this->value === $value) {
|
||||
return None::create();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return ArrayIterator<int, T> */
|
||||
public function getIterator(): ArrayIterator
|
||||
{
|
||||
return new ArrayIterator([$this->value]);
|
||||
}
|
||||
|
||||
public function foldLeft($initialValue, $callable): S
|
||||
{
|
||||
return $callable($initialValue, $this->value);
|
||||
}
|
||||
|
||||
public function foldRight($initialValue, $callable): S
|
||||
{
|
||||
return $callable($this->value, $initialValue);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user