brimmar / phpoption
Rust-like Option Type implementation for PHP
Requires
- php: >=8.1
Requires (Dev)
- pestphp/pest: ^2.34
README
本文档涵盖了为PHP实现类似Rust的Option类型的实现。Option类型用于表示可选值。它有两种变体:Some
,表示值的存在,和None
,表示值的缺失。
目录
Option接口
Option
接口定义了Some
和None
类的合约。
<?php namespace Brimmar\PhpOption\Interfaces; /** * @template T */ interface Option { // ... (methods will be documented below) }
使用方法
以下是一些展示Option类型使用和实用性的示例
示例1:用户配置文件管理
<?php use Brimmar\PhpOption\Some; use Brimmar\PhpOption\None; use Brimmar\PhpOption\Interfaces\Option; class UserProfile { private $data = []; public function setField(string $field, $value): void { $this->data[$field] = $value; } public function getField(string $field): Option { return isset($this->data[$field]) ? new Some($this->data[$field]) : new None(); } public function getDisplayName(): string { return $this->getField('display_name') ->orElse(fn() => $this->getField('username')) ->unwrapOr('Anonymous'); } public function getAge(): Option { return $this->getField('age') ->andThen(function($age) { return is_numeric($age) && $age > 0 && $age < 120 ? new Some((int)$age) : new None(); }); } } $profile = new UserProfile(); $profile->setField('username', 'johndoe'); $profile->setField('age', '30'); echo $profile->getDisplayName(); // Output: johndoe $age = $profile->getAge() ->map(fn($age) => "User is $age years old") ->unwrapOr("Age not provided or invalid"); echo $age; // Output: User is 30 years old $email = $profile->getField('email') ->map(fn($email) => "Contact: $email") ->unwrapOr("No email provided"); echo $email; // Output: No email provided
示例2:使用Option和Result进行配置管理
本例演示了Option和Result类型如何协同工作以进行健壮的配置管理。
<?php use Brimmar\PhpOption\Some; use Brimmar\PhpOption\None; use Brimmar\PhpResult\Ok; use Brimmar\PhpResult\Err; class ConfigManager { private $configs = []; public function getConfig(string $key): Option { return isset($this->configs[$key]) ? new Some($this->configs[$key]) : new None(); } public function setConfig(string $key, $value): void { $this->configs[$key] = $value; } public function getRequiredConfig(string $key): Result { return $this->getConfig($key) ->ok() ->mapErr(fn() => "Required configuration '$key' is missing"); } public function getDatabaseUrl(): Result { return $this->getRequiredConfig('database_url') ->andThen(function($url) { $parsed = parse_url($url); return isset($parsed['scheme'], $parsed['host'], $parsed['path']) ? new Ok($url) : new Err("Invalid database URL format"); }); } } $manager = new ConfigManager(); $manager->setConfig('database_url', 'mysql:///mydb'); $manager->setConfig('debug', true); $debugMode = $manager->getConfig('debug') ->unwrapOr(false); echo $debugMode ? "Debug mode is ON" : "Debug mode is OFF"; // Output: Debug mode is ON $databaseUrl = $manager->getDatabaseUrl() ->map(fn($url) => "Connected to: $url") ->unwrapOr("Failed to connect to database"); echo $databaseUrl; // Output: Connected to: mysql:///mydb $apiKey = $manager->getRequiredConfig('api_key') ->match( Ok: fn($key) => "API Key: $key", Err: fn($error) => "Error: $error", ); echo $apiKey; // Output: Error: Required configuration 'api_key' is missing
示例3:使用Option类型进行可选链
本例演示了如何使用Option类型安全地进行方法调用链,类似于其他语言中的可选链。
<?php use Brimmar\PhpOption\Some; use Brimmar\PhpOption\None; use Brimmar\PhpOption\Interfaces\Option; class Address { public function __construct(public string $street, public string $city, public string $country) {} } class User { public function __construct(public string $name, private ?Address $address = null) {} public function getAddress(): Option { return $this->address ? new Some($this->address) : new None(); } } class UserRepository { private $users = []; public function addUser(User $user): void { $this->users[] = $user; } public function findUserByName(string $name): Option { $user = array_values(array_filter($this->users, fn($u) => $u->name === $name))[0] ?? null; return $user ? new Some($user) : new None(); } } $repo = new UserRepository(); $repo->addUser(new User("Alice", new Address("123 Main St", "Springfield", "USA"))); $repo->addUser(new User("Bob")); function getUserCountry(UserRepository $repo, string $name): string { return $repo->findUserByName($name) ->andThen(fn($user) => $user->getAddress()) ->map(fn($address) => $address->country) ->unwrapOr("Country not found"); } echo getUserCountry($repo, "Alice"); // Output: USA echo getUserCountry($repo, "Bob"); // Output: Country not found echo getUserCountry($repo, "Charlie"); // Output: Country not found
方法
isSome(): bool
如果选项是Some
值,则返回true
。
示例
$option = new Some(42); echo $option->isSome(); // true $option = new None(); echo $option->isSome(); // false
isSomeAnd(callable $fn): bool
如果选项是Some
值且其内部值匹配谓词,则返回true
。
示例
$option = new Some(42); echo $option->isSomeAnd(fn($value) => $value > 40); // true echo $option->isSomeAnd(fn($value) => $value < 40); // false $option = new None(); echo $option->isSomeAnd(fn($value) => $value > 0); // false
isNone(): bool
如果选项是None
值,则返回true
。
示例
$option = new Some(42); echo $option->isNone(); // false $option = new None(); echo $option->isNone(); // true
iter(): Iterator
返回一个遍历可能包含的值的迭代器。
示例
$option = new Some(42); foreach ($option->iter() as $value) { echo $value; // 42 } $option = new None(); foreach ($option->iter() as $value) { // This block will never be executed }
unwrap(): mixed
返回包含的Some
值或如果值为None
则抛出异常。
示例
$option = new Some(42); echo $option->unwrap(); // 42 $option = new None(); $option->unwrap(); // Throws RuntimeException
expect(string $msg): mixed
返回包含的Some
值或如果值为None
则抛出一个带有自定义消息的异常。
示例
$option = new Some(42); echo $option->expect("Value should be present"); // 42 $option = new None(); $option->expect("Value is required"); // Throws RuntimeException with message "Value is required"
flatten(): Option
从Option<Option<T>>
转换为Option<T>
。
示例
$option = new Some(new Some(42)); $flattened = $option->flatten(); echo $flattened->unwrap(); // 42 $option = new Some(new None()); $flattened = $option->flatten(); echo $flattened->isNone(); // true
unwrapOr(mixed $default): mixed
返回包含的Some
值或提供的默认值。
示例
$option = new Some(42); echo $option->unwrapOr(0); // 42 $option = new None(); echo $option->unwrapOr(0); // 0
unwrapOrElse(callable $default): mixed
返回包含的Some
值或从闭包中计算它。
示例
$option = new Some(42); echo $option->unwrapOrElse(fn() => 0); // 42 $option = new None(); echo $option->unwrapOrElse(fn() => 0); // 0
map(callable $fn): Option
通过将函数应用于包含的值将Option<T>
映射到Option<U>
。
示例
$option = new Some(42); $mapped = $option->map(fn($x) => $x * 2); echo $mapped->unwrap(); // 84 $option = new None(); $mapped = $option->map(fn($x) => $x * 2); echo $mapped->isNone(); // true
mapOr(mixed $default, callable $fn): mixed
如果有的话,将函数应用于包含的值,或返回默认值。
示例
$option = new Some(42); echo $option->mapOr(0, fn($x) => $x * 2); // 84 $option = new None(); echo $option->mapOr(0, fn($x) => $x * 2); // 0
mapOrElse(callable $default, callable $fn): mixed
如果有的话,将函数应用于包含的值,或计算默认值。
示例
$option = new Some(42); echo $option->mapOrElse(fn() => 0, fn($x) => $x * 2); // 84 $option = new None(); echo $option->mapOrElse(fn() => 0, fn($x) => $x * 2); // 0
inspect(callable $fn): Option
如果Some
,则通过包含的值调用提供的闭包。
示例
$option = new Some(42); $option->inspect(function($x) { echo "Got: $x"; }); // Outputs: Got: 42 echo $option->unwrap(); // 42 $option = new None(); $option->inspect(function($x) { echo "Got: $x"; }); // No output
okOr(mixed $error, ?string $okClassName = '\Brimmar\PhpResult\Ok'): mixed
将Option<T>
转换为Result<T, E>
,将Some(v)
映射到Ok(v)
,将None
映射到Err(error)
。
示例
$option = new Some(42); $result = $option->okOr("No value"); echo $result->unwrap(); // 42 $option = new None(); $result = $option->okOr("No value"); echo $result->unwrapErr(); // "No value"
okOrElse(callable $error, ?string $okClassName = '\Brimmar\PhpResult\Ok'): mixed
将Option<T>
转换为Result<T, E>
,将Some(v)
映射到Ok(v)
,将None
映射到Err(error())
。
示例
$option = new Some(42); $result = $option->okOrElse(fn() => "No value"); echo $result->unwrap(); // 42 $option = new None(); $result = $option->okOrElse(fn() => "No value"); echo $result->unwrapErr(); // "No value"
and(Option $opt): Option
如果选项是None
,则返回None
,否则返回opt
。
示例
$option1 = new Some(42); $option2 = new Some(10); $result = $option1->and($option2); echo $result->unwrap(); // 10 $option1 = new None(); $option2 = new Some(10); $result = $option1->and($option2); echo $result->isNone(); // true
andThen(callable $fn): Option
如果选项是None
,则返回None
,否则调用fn
并将包装的值作为参数,然后返回结果。
示例
$option = new Some(42); $result = $option->andThen(fn($x) => new Some($x * 2)); echo $result->unwrap(); // 84 $option = new None(); $result = $option->andThen(fn($x) => new Some($x * 2)); echo $result->isNone(); // true
or(Option $opt): Option
如果选项包含值,则返回该选项,否则返回opt
。
示例
$option1 = new Some(42); $option2 = new Some(10); $result = $option1->or($option2); echo $result->unwrap(); // 42 $option1 = new None(); $option2 = new Some(10); $result = $option1->or($option2); echo $result->unwrap(); // 10
orElse(callable $fn): Option
如果选项包含值,则返回该选项,否则调用fn
并返回结果。
示例
$option = new Some(42); $result = $option->orElse(fn() => new Some(10)); echo $result->unwrap(); // 42 $option = new None(); $result = $option->orElse(fn() => new Some(10)); echo $result->unwrap(); // 10
transpose(?string $okClassName = '\Brimmar\PhpResult\Ok', ?string $errClassName = '\Brimmar\PhpResult\Err'): mixed
将一个 Result
的 Option
转换为一个 Option
的 Result
.
示例
$option = new Some(new Ok(42)); $result = $option->transpose(); echo $result->unwrap()->unwrap(); // 42 $option = new Some(new Err("error")); $result = $option->transpose(); echo $result->unwrapErr(); // "error" $option = new None(); $result = $option->transpose(); echo $result->unwrap()->isNone(); // true
xor(Option $opt): Option
如果 $this
或 $opt
中恰好有一个是 Some
,则返回 Some
,否则返回 None
。
示例
$option1 = new Some(42); $option2 = new None(); $result = $option1->xor($option2); echo $result->unwrap(); // 42 $option1 = new Some(42); $option2 = new Some(10); $result = $option1->xor($option2); echo $result->isNone(); // true
zip(Option $other): Option
将 $this
与另一个 Option
进行组合。
示例
$option1 = new Some(42); $option2 = new Some("hello"); $result = $option1->zip($option2); print_r($result->unwrap()); // [42, "hello"] $option1 = new Some(42); $option2 = new None(); $result = $option1->zip($option2); echo $result->isNone(); // true
zipWith(Option $other, callable $fn): Option
使用函数 $fn
将 $this
和另一个 Option
进行组合。
示例
$option1 = new Some(42); $option2 = new Some(10); $result = $option1->zipWith($option2, fn($a, $b) => $a + $b); echo $result->unwrap(); // 52 $option1 = new Some(42); $option2 = new None(); $result = $option1->zipWith($option2, fn($a, $b) => $a + $b); echo $result->isNone(); // true
unzip(): array
解包包含两个 Option
元组的 Option
。
示例
$option = new Some([42, "hello"]); [$a, $b] = $option->unzip(); echo $a->unwrap(); // 42 echo $b->unwrap(); // "hello" $option = new None(); [$a, $b] = $option->unzip(); echo $a->isNone(); // true echo $b->isNone(); // true
match(callable $Some, callable $None): mixed
应用一个函数来检索包含的值。
示例
$option = new Some(42); $result = $option->match( Some: fn($x) => "Value is $x", None: fn() => "No value" ); echo $result; // "Value is 42" $option = new None(); $result = $option->match( Some: fn($x) => "Value is $x", None: fn() => "No value" ); echo $result; // "No value"
filter(callable $predicate): Option
如果 option
是 None
,则返回 None
,否则调用 predicate
并使用包装的值作为参数,并返回
Some(t)
如果predicate
返回true
(其中t
是包装的值)None
如果predicate
返回false
示例
$option = new Some(42); $result = $option->filter(fn($x) => $x > 40); echo $result->unwrap(); // 42 $option = new Some(42); $result = $option->filter(fn($x) => $x < 40); echo $result->isNone(); // true $option = new None(); $result = $option->filter(fn($x) => $x > 0); echo $result->isNone(); // true
互补包
此包与 PHP 结果类型包配合良好,该包实现了结果类型。此包中的一些方法,如 okOr
和 okOrElse
,返回结果类型。
静态分析
我们建议使用 PHPStan 进行静态代码分析。此包包含自定义 PHPStan 规则,以增强 Option 类型的类型检查。要启用这些规则,请在您的 PHPStan 配置中添加以下内容
composer require brimmar/phpstan-rustlike-option-extension --dev
// phpstan.neon includes: - vendor/brimmar/phpstan-rustlike-option-extension/extension.neon
贡献
有关详细信息,请参阅 CONTRIBUTING.md
安全漏洞
有关如何报告安全漏洞的详细信息,请参阅我们的 安全策略
许可
此项目采用 MIT 许可证。有关更多信息,请参阅 LICENSE.md