unicorn-fail/php-option

一个高度可扩展的 [phpoption/phpoption] 替代品,支持 `TypedOption`。

1.6.0 2019-08-07 08:57 UTC

This package is auto-updated.

Last update: 2024-09-08 05:57:12 UTC


README

一个高度可扩展的 [phpoption/phpoption](https://packagist.org.cn/packages/phpoption/phpoption) 替代品,支持 TypedOption

Latest Version Total Downloads License PHP from Packagist

Build Status Codacy grade Codacy coverage phpcs coding standard

Option 用于在根据参数或其他运行时因素有时返回值(通常是对象)和有时不返回值(通常是 null)的情况下。

通常情况下,你会忘记处理不返回值的情况。当然不是故意的,但可能你没有考虑到系统的所有可能状态;或者你可能真的覆盖了所有情况,但随着时间的推移,代码被重构,其中一些检查可能变得无效或不完整。突然之间,你可能会在不知不觉中忘记处理没有值的情况。结果,你有时会收到致命的 PHP 错误,告诉你在一个非对象上调用了一个方法;用户可能会看到空白页面,更糟糕的是。

一方面,Option 强迫开发者有意识地思考两种情况(返回值或不返回值)。这本身就会让你的代码更健壮。另一方面,Option 还允许 API 开发者提供更简洁的 API 方法,并赋予 API 用户消费这些方法的能力。

📦 安装

该项目可以通过 Composer 安装。

$ composer require unicorn-fail/php-option

基本用法

在您的 API 中使用 Option

<?php

use UnicornFail\PhpOption\None;
use UnicornFail\PhpOption\Some;

class MyRepository
{
    public function findSomeEntity($criteria)
    {
        if (null !== $entity = $this->entityManager->find($criteria)) {
            return Some::create($entity);
        }

        // Use a singleton for the None case (it's statically cached for performance).
        return None::create();
    }
}

如果您正在使用现有的库,您还可以使用一个简短的版本,该版本默认将 null 视为 None,将其他所有内容视为 Some 情况。

之后

<?php

use UnicornFail\PhpOption\Option;

class MyRepository
{
    public function findSomeEntity($criteria)
    {
        return Option::create($this->entityManager->find($criteria));

        // Or, if you want to change the none value to false for example:
        return Option::create($this->em->find($criteria), ['noneValue' => false]);
    }
}

情况 1:在调用代码时始终需要实体

$entity = $repo->findSomeEntity($criteria)->get(); // Returns an Entity, or throws exception.

情况 2:如果不可用,则回退到默认值

$entity = $repo->findSomeEntity($criteria)->getOrElse(new Entity);

// Or, if you need to lazily create the entity.
$entity = $repo->findSomeEntity($criteria)->getOrCall(function() {
    return new Entity;
});

不再需要样板代码

之前

$entity = $this->findSomeEntity();
if ($entity === null) {
    throw new NotFoundException();
}
return $entity->name;

之后

return $this->findSomeEntity()->get()->name;

不再有控制流异常

之前

try {
    $entity = $this->findSomeEntity();
} catch (NotFoundException $ex) {
    $entity = new Entity;
}

之后

$entity = $this->findSomeEntity()->getOrElse(new Entity);

简洁的 null 处理

之前

$entity = $this->findSomeEntity();
if ($entity === null) {
    return new Entity;
}

return $entity;

之后

return $this->findSomeEntity()->getOrElse(new Entity);

链式多个替代选项

如果您需要尝试多个替代方案,orElse 方法允许您优雅地完成。

之前

$entity = $this->findSomeEntity();
if ($entity === null) {
    $entity = $this->findSomeOtherEntity();
    if ($entity === null) {
        $entity = $this->createEntity();
    }
}
return $entity;

之后

return $this->findSomeEntity()
            ->orElse($this->findSomeOtherEntity())
            ->orElse($this->createEntity());

返回第一个非空选项。这对于惰性评估的选项特别有用。

惰性评估的选项

上述示例中存在一个缺陷,即选项链需要在方法调用时评估所有选项。如果第一个选项已经非空,这会产生不必要的开销。

幸运的是,这可以通过使用 LazyOption 来解决,它仅在实际需要时调用一个可调用对象。

use UnicornFail\PhpOption\LazyOption;

return $this->findSomeEntity()
            ->orElse(LazyOption::create([$this, 'findSomeOtherEntity']))
            ->orElse(LazyOption::create([$this, 'createEntity']));

类型化选项

在您需要返回特定 PHP 类型(例如字符串、布尔值、数字等)的情况下,TypedOption 类可能为您提供更多灵活性。

之前

// ?coords=32:43,35:22,94:33,95:34
$coordsStr = !!(isset($_GET['coords']) ? $_GET['coords'] : '');
$coords = $coordsStr ? array_map('trim', explode(',', $coordsStr)) : [];
foreach ($coords as $coord) {
    list ($x, $y) = array_map('trim', explode(':', $coord));
    $this->doSomething($x, $y);
}

之后

use UnicornFail\PhpOption\TypedOption;

// Automatically parsed by the SomeArray typed option.
// ?coords=32:43,35:22,94:33,95:34
$items = TypedOption::pick($_GET, 'coords', ['keyDelimiter' => ':'])->getOrElse([]);
foreach ($items as $x => $y) {
    $this->doSomething($x, $y);
}

📓 API 文档

官方和详细的 API 文档即将推出 (PRs 受欢迎)。

🏷️ 版本控制

严格遵循 SemVer。次要和补丁版本不应引入代码库的破坏性更改。

该项目最初发布将从版本 1.6.0 开始,以与现有的 [phpoption/phpoption](https://packagist.org.cn/packages/phpoption/phpoption) 发布保持一致。

🛠️ 支持 & 向后兼容性

版本 <1.6.0

  • 本项目将不会修复任何错误、解决任何安全问题或进行其他发布。请升级,这应该像以下这样简单
    $ composer remove phpoption/phpoption
    $ composer require unicorn-fail/php-option

版本 >=1.6.0 <2.0.0

  • 本项目将保持与 phpoption/phpoption 的向后兼容性,并继续运行 原始测试,以确保之前的命名空间和实现仍然有效。
  • 以下类将自动注册为别名,因此现有代码应该仍然有效(请参阅下面的已知问题)
    • PhpOption\LazyOption <=> UnicornFail\PhpOption\LazyOption
    • PhpOption\None <=> UnicornFail\PhpOption\None
    • PhpOption\Option <=> UnicornFail\PhpOption\Option
    • PhpOption\Some <=> UnicornFail\PhpOption\Some
  • 以下方法已弃用,请使用它们的替代品
    • Option::ensure() <=> Option::create()
    • Option::fromValue() <=> Option::create()
    • Option::fromArraysValue() <=> Option::pick()
    • Option::fromReturn() <=> Option::create()
    • $option->ifDefined() <=> $option->forAll()
  • 已知问题
    • 由于测试/环境问题,原始测试包含非常 微小的修改
    • PHP 5.3 存在一个奇怪的漏洞,其中类别名没有正确注册。因此,不得不从各自的替代品扩展这些类。遗憾的是,这阻止了 \PhpOption\Some\PhpOption\None\PhpOption\LazyOption 直接从 \PhpOption\Option 扩展。如果您代码中实现了类似 $option instanceof Option 的内容,这些将失败。您需要将这些更改为 $option instanceof \UnicornFail\PhpOption\OptionInterface

版本 2.0.0

  • 本项目计划移除对所有 PHP 5PHP 7.0PHP 7.1 的支持,以及与 phpoption/phpoption 的向后兼容性。

⛔ 安全性

要报告安全漏洞,请使用 Tidelift 安全联系人。Tidelift 将与我们一起协调修复和披露。

👷‍♀️ 贡献

对本库的贡献是 欢迎的

有关更多信息,请参阅 CONTRIBUTING

🧪 测试

本地开发(忽略对 composer.json 的更改)

$ composer require-test
$ composer test

包含覆盖率

$ composer require-test
$ composer require-coverage
$ composer test-coverage

🚀 性能基准

当然,性能很重要。测试中包含了一个性能基准,您可以在您选择的机器上运行

$ composer test-group performance

在核心上,使用 Option 产生的开销主要是创建一个对象,即 Option 包装器所花费的时间。它还需要执行一个额外的方法调用以从包装器检索值。根据您的用例和所需功能,您可能会遇到不同的结果。

每次调用的平均开销*

如表上所示,这些基准很少低于微秒的几分之一。其中许多测量在纳秒级别;随着较新版本的 PHP 逐渐降低开销。

除非您计划在请求期间调用方法数十万次,否则没有必要坚持使用 object|null 返回值;最好给您的代码一些选择!

*基于创建单个对象与创建包装器和单个方法调用的比较;迭代 10000 次,然后计算差异的平均值。

👥 致谢

📄 许可证

unicorn-fail/php-option 采用 Apache 2.0 许可证。更多详情请参阅 LICENSE 文件。