brimmar/phpoption

Rust-like Option Type implementation for PHP

v1.0.0 2024-08-01 20:37 UTC

This package is auto-updated.

Last update: 2024-10-02 14:51:24 UTC


README

本文档涵盖了为PHP实现类似Rust的Option类型的实现。Option类型用于表示可选值。它有两种变体:Some,表示值的存在,和None,表示值的缺失。

目录

  1. Option接口
  2. 使用方法
  3. 方法
  4. 互补包
  5. 静态分析
  6. 贡献
  7. 安全漏洞
  8. 许可

Option接口

Option接口定义了SomeNone类的合约。

<?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

将一个 ResultOption 转换为一个 OptionResult.

示例

$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

如果 optionNone,则返回 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 结果类型包配合良好,该包实现了结果类型。此包中的一些方法,如 okOrokOrElse,返回结果类型。

PhpResult

静态分析

我们建议使用 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