OpenFeature SDK 的 PHP 实现

2.0.8 2024-08-05 03:09 UTC

README

OpenFeature PHP SDK

Specification Release
Total Downloads PHP 8.0+ License OpenSSF Best Practices

OpenFeature 是一个开放规范,它提供了一个供应商无关、社区驱动的 API,用于功能标志,可与您喜欢的功能标志管理工具协同工作。

🚀 快速入门

需求

此库针对 PHP 版本 8.0 及更高版本。只要您系统上有任何兼容版本的 PHP,就应该能够使用 OpenFeature SDK。

此包还包括一个 .tool-versions 文件,用于与 asdf 等版本管理器一起使用。

安装

composer require open-feature/sdk

用法

use OpenFeature\OpenFeatureAPI;
use OpenFeature\Providers\Flagd\FlagdProvider;

function example()
{
    $api = OpenFeatureAPI::getInstance();
    
    // configure a provider
    $api->setProvider(new FlagdProvider());

    // create a client
    $client = $api->getClient();
    
    // get a bool flag value
    $client->getBooleanValue('v2_enabled', false);
}

扩展示例

use OpenFeature\OpenFeatureClient;

class MyClass 
{
  private OpenFeatureClient $client;

  public function __construct() 
  {
    $this->client = OpenFeatureAPI::getInstance()->getClient('MyClass');
  }

  public function booleanExample(): UI
  {
      // Should we render the redesign? Or the default webpage? 
      if ($this->client->getBooleanValue('redesign_enabled', false)) {
          return render_redesign();
      }
      return render_normal();
  }

  public function stringExample(): Template
  {
      // Get the template to load for the custom new homepage
      $template = $this->client->getStringValue('homepage_template', 'default-homepage.html');

      return render_template($template);
  }

  public function numberExample(): array
  {
      // How many modules should we be fetching?
      $count = $this->client->getIntegerValue('module-fetch-count', 4);

      return fetch_modules($count);
  }

  public function structureExample(): HomepageModule
  {
      $obj = $this->client->getObjectValue('hero-module', $previouslyDefinedDefaultStructure);

      return HomepageModuleBuilder::new()
              ->title($obj->getValue('title'))
              ->body($obj->getValue('description'))
              ->build();
  }
}

🌟 功能

已实现:✅ | 进行中:⚠️ | 尚未实现:❌

提供者

提供者 是一个抽象层,位于标志管理系统和 OpenFeature SDK 之间。查看此处以获取可用的提供者的完整列表。如果您正在寻找的提供者尚未创建,请参阅开发提供者部分以了解如何自行构建。

一旦将提供者添加为依赖项,就可以像这样将其注册到 OpenFeature 中

$api = OpenFeatureAPI::getInstance();
$api->setProvider(new MyProvider());

定位

有时,标志的值必须考虑与应用程序或用户的一些动态标准,例如用户的地理位置、IP、电子邮件地址或服务器的位置。在 OpenFeature 中,我们将其称为 定位。如果您正在使用的标志管理系统支持定位,您可以使用 评估上下文 提供输入数据。

// add a value to the global context
$api = OpenFeatureAPI::getInstance();
$api->setEvaluationContext(new EvaluationContext('targetingKey', ['myGlobalKey' => 'myGlobalValue']));

// add a value to the client context
$client = $api->getClient();
$client->setEvaluationContext(new EvaluationContext('targetingKey', ['myClientKey' => 'myClientValue']));

// add a value to the invocation context
$context = new EvaluationContext('targetingKey', ['myInvocationKey' => 'myInvocationValue']);

$boolValue = $client->getBooleanValue('boolFlag', false, $context);

钩子

钩子 允许在标志评估生命周期的定义良好的点上添加自定义逻辑。查看此处以获取可用的钩子的完整列表。如果您正在寻找的钩子尚未创建,请参阅开发钩子部分以了解如何自行构建。

一旦将钩子添加为依赖项,就可以在全局、客户端或标志调用级别进行注册。

// add a hook globally, to run on all evaluations
$api = OpenFeatureAPI::getInstance();
$api->addHook(new ExampleGlobalHook());

// add a hook on this client, to run on all evaluations made by this client
$client = $api->getClient();
$client->addHook(new ExampleClientHook());

// add a hook for this evaluation only
$value = $client->getBooleanValue("boolFlag", false, $context, new EvaluationOptions([new ExampleInvocationHook()]));

日志记录

PHP SDK 使用了多个 PHP 标准建议,其中之一是 PSR-3,它提供了一个标准的 LoggerInterface。SDK 在几个组件上使用了 LoggerAwareTrait,包括用于标志评估的客户端、钩子执行器和全局 OpenFeatureAPI 实例。当 API 创建 OpenFeature 客户端时,它将自动使用 API 中的配置记录器。客户端中设置的记录器也将自动用于钩子执行。

⚠️ 一旦客户端实例化后,API 日志器的更新将不会同步。这样做是为了支持命名客户端的分离。如果您必须更新现有客户端的日志器,请直接更新!

$api = OpenFeatureAPI::getInstance();

$logger = new FancyLogger();

$defaultLoggerClient = $api->getClient('default-logger');

$api->setLogger(new CustomLogger());

$customLoggerClient = $api->getClient('custom-logger');

$overrideLoggerClient = $api->getClient('override-logger');
$overrideLoggerClient->setLogger($logger);

// now let's do some evaluations with these!

$defaultLoggerClient->getBooleanValue('A', false);
// uses default logger in the SDK

$customLoggerClient->getBooleanValue('B', false);
// uses the CustomLogger set in the API before the client was made

$overrideLoggerClient->getBooleanValue('C', false);
// uses the FancyLogger set directly on the client

命名客户端

在 PHP SDK 中尚未提供命名客户端。您可以在此跟踪该功能的进展 这里

事件处理

在 PHP SDK 中尚未提供事件处理功能。您可以在此跟踪该功能的进展 这里

关闭

PHP SDK 中尚未提供关闭方法。您可以在此跟踪该功能的进展 这里

扩展

开发提供者

要开发提供者,您需要创建一个新项目并将 OpenFeature SDK 作为依赖项包含在内。这可以是新存储库或包含在 OpenFeature 组织下的现有 contrib 存储库 中。然后,您需要通过实现 OpenFeature SDK 导出的 Provider 接口来编写提供者。

declare(strict_types=1);

namespace OpenFeature\Example\Providers;

use OpenFeature\implementation\common\Metadata;
use OpenFeature\interfaces\common\Metadata as IMetadata;
use OpenFeature\interfaces\flags\EvaluationContext;
use OpenFeature\interfaces\hooks\Hook;
use OpenFeature\interfaces\provider\Provider;
use OpenFeature\interfaces\provider\ResolutionDetails;

class ExampleProviderImplementation implements Provider
{
    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;

        // or, utilize the OpenFeature\interfaces\common\LoggerAwareTrait
    }

    /**
     * @return Hook[]
     */
    public function getHooks(): array
    {
        return $this->hooks; // implement according to the OpenFeature specification
    }

    /**
     * Returns the metadata for the current resource
     */
    public function getMetadata(): IMetadata
    {
        return new Metadata(self::class);
    }

    public function resolveBooleanValue(string $flagKey, bool $defaultValue, ?EvaluationContext $context = null): ResolutionDetails
    {
        // resolve some ResolutionDetails
    }

    public function resolveStringValue(string $flagKey, string $defaultValue, ?EvaluationContext $context = null): ResolutionDetails
    {
        // resolve some ResolutionDetails
    }

    public function resolveIntegerValue(string $flagKey, int $defaultValue, ?EvaluationContext $context = null): ResolutionDetails
    {
        // resolve some ResolutionDetails
    }

    public function resolveFloatValue(string $flagKey, float $defaultValue, ?EvaluationContext $context = null): ResolutionDetails
    {
        // resolve some ResolutionDetails
    }

    /**
     * @inheritdoc
     */
    public function resolveObjectValue(string $flagKey, array $defaultValue, ?EvaluationContext $context = null): ResolutionDetails
    {
        // resolve some ResolutionDetails
    }
}

如您所见,这最终需要一些样板代码来实现提供者期望的所有功能。实现提供者的另一种选项是利用 AbstractProvider 基类。这提供了一些内部连接和简单的脚手架,这样您就可以跳过其中一些内容,并专注于最重要的内容:解析功能标志!

declare(strict_types=1);

namespace OpenFeature\Example\Providers;

use OpenFeature\interfaces\flags\EvaluationContext;
use OpenFeature\interfaces\provider\ResolutionDetails;

class ExampleProviderExtension extends AbstractProvider
{
    protected static string $NAME = self::class;

    public function resolveBooleanValue(string $flagKey, bool $defaultValue, ?EvaluationContext $context = null): ResolutionDetailsInterface
    {
        // resolve some ResolutionDetails
    }

    public function resolveStringValue(string $flagKey, string $defaultValue, ?EvaluationContext $context = null): ResolutionDetailsInterface
    {
        // resolve some ResolutionDetails
    }

    public function resolveIntegerValue(string $flagKey, int $defaultValue, ?EvaluationContext $context = null): ResolutionDetailsInterface
    {
        // resolve some ResolutionDetails
    }

    public function resolveFloatValue(string $flagKey, float $defaultValue, ?EvaluationContext $context = null): ResolutionDetailsInterface
    {
        // resolve some ResolutionDetails
    }

    /**
     * @inheritdoc
     */
    public function resolveObjectValue(string $flagKey, array $defaultValue, ?EvaluationContext $context = null): ResolutionDetailsInterface
    {
        // resolve some ResolutionDetails
    }
}

已构建新的提供者? 告诉我们,这样我们就可以将其添加到文档中!

开发钩子

要开发钩子,您需要创建一个新项目并将 OpenFeature SDK 作为依赖项包含在内。这可以是新存储库或包含在 OpenFeature 组织下的现有 contrib 存储库 中。通过符合 Hook interface 实现自己的钩子。为了满足接口,需要定义所有方法(Before/After/Finally/Error)。为了避免定义空函数,请使用 UnimplementedHook 结构(该结构已经实现了所有空函数)。

declare(strict_types=1);

namespace OpenFeature\Example\Hooks;

use OpenFeature\interfaces\flags\EvaluationContext;
use OpenFeature\interfaces\flags\FlagValueType;
use OpenFeature\interfaces\hooks\Hook;
use OpenFeature\interfaces\hooks\HookContext;
use OpenFeature\interfaces\hooks\HookHints;
use OpenFeature\interfaces\provider\ResolutionDetails;


class ExampleStringHookImplementation implements Hook
{
    public function before(HookContext $context, HookHints $hints): ?EvaluationContext
    {
    }

    public function after(HookContext $context, ResolutionDetails $details, HookHints $hints): void
    {
    }

    public function error(HookContext $context, Throwable $error, HookHints $hints): void
    {
    }

    public function finally(HookContext $context, HookHints $hints): void
    {
    }


    public function supportsFlagValueType(string $flagValueType): bool
    {
        return $flagValueType === FlagValueType::STRING;
    }
}

您还可以使用现有基类来实现各种类型和行为。假设您想创建相同的钩子,并且没有扩展基类的限制,您可以这样做

declare(strict_types=1);

namespace OpenFeature\Example\Hooks;

use OpenFeature\implementation\hooks\StringHook;
use OpenFeature\interfaces\flags\EvaluationContext;
use OpenFeature\interfaces\flags\FlagValueType;
use OpenFeature\interfaces\hooks\HookContext;
use OpenFeature\interfaces\hooks\HookHints;
use OpenFeature\interfaces\provider\ResolutionDetails;


class ExampleStringHookExtension extends StringHook
{
    public function before(HookContext $context, HookHints $hints): ?EvaluationContext
    {
    }

    public function after(HookContext $context, ResolutionDetails $details, HookHints $hints): void
    {
    }

    public function error(HookContext $context, Throwable $error, HookHints $hints): void
    {
    }

    public function finally(HookContext $context, HookHints $hints): void
    {
    }
}

已构建新的钩子? 告诉我们,这样我们就可以将其添加到文档中!

⭐️ 支持项目

🤝 贡献

有兴趣贡献?太好了,我们非常乐意您的帮助!要开始,请查看 CONTRIBUTING 指南。

感谢所有已经做出贡献的人

Pictures of the folks who have contributed to the project

使用 contrib.rocks 制作。