rikudou / unleash-sdk
Requires
- php: ^8.0
- ext-json: *
- lastguest/murmurhash: ^2.1
- psr/http-client: ^1.0
- psr/http-client-implementation: ^1.0
- psr/http-factory: ^1.0
- psr/http-factory-implementation: ^1.0
- psr/http-message: ^1.0
- psr/simple-cache: ^1.0
- psr/simple-cache-implementation: ^1.0
Requires (Dev)
- cache/filesystem-adapter: ^1.1
- friendsofphp/php-cs-fixer: ^3.0
- guzzlehttp/guzzle: ^7.0
- jetbrains/phpstorm-attributes: ^1.0
- nyholm/psr7: ^1.0
- phpstan/phpstan: ^0.12.90
- phpunit/phpunit: ^9.5
- rector/rector: ^0.11.32
- symfony/cache: ^5.0
- symfony/http-client: ^5.0
Suggests
- guzzlehttp/guzzle: A http client implementation (PSR-17 and PSR-18)
- nyholm/psr7: Needed when you use symfony/http-client
- symfony/http-client: A http client implementation (PSR-17 and PSR-18)
- dev-master
- v1.1.180
- v1.1.174
- v1.1.173
- v1.1.080
- v1.1.074
- v1.1.073
- v1.0.180
- v1.0.174
- v1.0.173
- v1.0.080
- v1.0.074
- v1.0.073
- v0.12.1.80
- v0.12.1.74
- v0.12.1.73
- v0.12.0.80
- v0.12.0.73
- v0.11.1.80
- v0.11.1.73
- v0.11.0.80
- v0.11.0.73
- v0.10.1.80
- v0.10.1.73
- v0.10.0.80
- v0.10.0.73
- v0.9.11.80
- v0.9.11.73
- v0.9.10.80
- v0.9.10.73
- v0.9.9.80
- v0.9.9.73
- v0.9.2.80
- v0.9.2.73
- v0.9.1.80
- v0.9.1.73
- v0.9.0
- dev-code-coverage
- dev-php-8.1
- dev-php-7.3
- dev-php-7.4
This package is auto-updated.
Last update: 2021-08-18 09:31:40 UTC
README
PHP 实现的 Unleash 协议,也称为 GitLab 中的 功能标志。
您可能还对此软件包的 Symfony 扩展包 感兴趣。
此实现符合官方 Unleash 标准,并实现了所有 Unleash 功能。
Unleash 允许您在完全发布之前逐步发布应用程序的功能,基于多种策略,例如仅向特定用户发布或向用户总数的百分比发布。在上述链接的文档中了解更多信息。
安装
composer require rikudou/unleash-sdk
需要 PHP 7.3 或更高版本。
您还需要一些 PSR-18 和 PSR-17 的实现,例如 Guzzle 和 PSR-16,例如 Symfony Cache。示例
composer require rikudou/unleash-sdk guzzlehttp/guzzle symfony/cache
或者
composer require rikudou/unleash-sdk symfony/http-client nyholm/psr7 symfony/cache
使用方法
基本用法是获取 Unleash
对象并检查功能
<?php use Rikudou\Unleash\UnleashBuilder; $unleash = UnleashBuilder::create() ->withAppName('Some app name') ->withAppUrl('https://some-app-url.com') ->withInstanceId('Some instance id') ->build(); if ($unleash->isEnabled('some-feature-name')) { // do something }
您也可以(在某些情况下必须)提供一个上下文对象。如果功能在服务器上不存在,您将从 isEnabled()
获取 false
,但您可以更改默认值到 true
。
<?php use Rikudou\Unleash\UnleashBuilder; use Rikudou\Unleash\Configuration\UnleashContext; $unleash = UnleashBuilder::create() ->withAppName('Some app name') ->withAppUrl('https://some-app-url.com') ->withInstanceId('Some instance id') ->build(); $context = new UnleashContext( currentUserId: 'some-user-id-from-app', ipAddress: '127.0.0.1', // will be populated automatically from $_SERVER if needed sessionId: 'sess-123456', // will be populated automatically via session_id() if needed ); // or using pre php 8 style: $context = (new UnleashContext()) ->setCurrentUserId('some-user-id-from-app') ->setIpAddress('127.0.0.1') ->setSessionId('sess-123456'); if ($unleash->isEnabled('some-feature', $context)) { // do something } // changing the default value for non-existent features if ($unleash->isEnabled('nonexistent-feature', $context, true)) { // do something }
构建器
构建器包含许多配置选项,建议始终使用构建器来构建 Unleash 实例。构建器是不可变的。
可以使用 create()
静态方法或使用其构造函数来创建构建器对象
<?php use Rikudou\Unleash\UnleashBuilder; // both are the same $builder = new UnleashBuilder(); $builder = UnleashBuilder::create();
必需参数
根据规范,应用程序名称、实例 ID 和应用程序 URL 是必需的。
<?php use Rikudou\Unleash\UnleashBuilder; $builder = UnleashBuilder::create() ->withAppName('Some app name') ->withAppUrl('https://some-app-url.com') ->withInstanceId('Some instance id');
如果您正在使用 Unleash v4,您还需要指定授权密钥(API 密钥),您可以通过自定义头来实现。
<?php use Rikudou\Unleash\UnleashBuilder; $builder = UnleashBuilder::create() ->withAppName('Some app name') ->withAppUrl('https://some-app-url.com') ->withInstanceId('Some instance id') ->withHeader('Authorization', 'my-api-key');
可选参数
可以设置一些可选参数,包括
- HTTP 客户端实现
- 请求工厂实现
- 缓存实现(《PSR-16》)
- 缓存 TTL
- 可用策略
- HTTP 头
如果您使用guzzlehttp/guzzle或symfony/http-client(结合nyholm/psr7),则http客户端和请求工厂将自动创建,否则您需要自行提供实现。
如果您使用symfony/cache或cache/filesystem-adapter作为缓存实现,则缓存处理程序将自动创建,否则您需要自行提供一些实现。
<?php use Cache\Adapter\Filesystem\FilesystemCachePool; use League\Flysystem\Adapter\Local; use League\Flysystem\Filesystem; use GuzzleHttp\Client; use GuzzleHttp\Psr7\HttpFactory; use Rikudou\Unleash\Stickiness\MurmurHashCalculator; use Rikudou\Unleash\Strategy\DefaultStrategyHandler; use Rikudou\Unleash\Strategy\GradualRolloutStrategyHandler; use Rikudou\Unleash\Strategy\IpAddressStrategyHandler; use Rikudou\Unleash\Strategy\UserIdStrategyHandler; use Rikudou\Unleash\UnleashBuilder; $builder = UnleashBuilder::create() ->withAppName('Some app name') ->withAppUrl('https://some-app-url.com') ->withInstanceId('Some instance id') // now the optional ones ->withHttpClient(new Client()) ->withRequestFactory(new HttpFactory()) ->withCacheHandler(new FilesystemCachePool( // example with using cache/filesystem-adapter new Filesystem( new Local(sys_get_temp_dir()), ), ), 30) // the second parameter is time to live in seconds ->withCacheTimeToLive(60) // you can also set the cache time to live separately // if you don't add any strategies, by default all strategies are added ->withStrategies( // this example includes all available strategies new DefaultStrategyHandler(), new GradualRolloutStrategyHandler(new MurmurHashCalculator()), new IpAddressStrategyHandler(), new UserIdStrategyHandler(), ) // add headers one by one, if you specify a header with the same name multiple times it will be replaced by the // latest value ->withHeader('My-Custom-Header', 'some-value') ->withHeader('Some-Other-Header', 'some-other-value') // you can specify multiple headers at the same time, be aware that this REPLACES all the headers ->withHeaders([ 'Yet-Another-Header' => 'and-another-value', ]);
缓存
每次检查功能是否启用时都执行HTTP请求将会很慢,尤其是在热门应用中。这就是为什么这个库内置了对PSR-16缓存实现的支撑。
如果您没有提供任何实现并且存在默认实现,则使用默认实现,否则您将得到一个异常。您还可以提供TTL,默认为30秒。
开箱即用的缓存实现(意味着您不需要进行任何配置)
<?php use Cache\Adapter\Filesystem\FilesystemCachePool; use League\Flysystem\Adapter\Local; use League\Flysystem\Filesystem; use Rikudou\Unleash\UnleashBuilder; $builder = UnleashBuilder::create() ->withCacheHandler(new FilesystemCachePool( // example with using cache/filesystem-adapter new Filesystem( new Local(sys_get_temp_dir()), ), )) ->withCacheTimeToLive(120); // you can set the cache handler explicitly to null to revert back to autodetection $builder = $builder ->withCacheHandler(null);
策略
Unleash服务器可以使用多个策略来启用或禁用功能。哪个策略被使用是在服务器上定义的。此实现支持所有非弃用的v4策略,除了主机名。《更多信息请参考此处》[https://docs.getunleash.io/user_guide/activation_strategy](https://docs.getunleash.io/user_guide/activation_strategy)。
默认策略
这是最简单的一种,如果功能将其默认策略定义为true,并且不需要任何上下文参数,则始终返回true。
IP地址策略
根据IP地址启用功能。从上下文对象中获取当前用户的IP地址。您可以提供自己的IP地址或使用默认值(`$_SERVER['REMOTE_ADDR']`)。提供自己的IP地址特别有用,如果您位于代理后面,因此`REMOTE_ADDR`将返回您的代理服务器的IP地址。
<?php use Rikudou\Unleash\UnleashBuilder; use Rikudou\Unleash\Configuration\UnleashContext; $unleash = UnleashBuilder::create() ->withAppName('Some app name') ->withAppUrl('https://some-app-url.com') ->withInstanceId('Some instance id') ->build(); // without context, using the auto detected IP $enabled = $unleash->isEnabled('some-feature'); // with context $context = new UnleashContext(ipAddress: $_SERVER['HTTP_X_FORWARDED_FOR']); // or pre php 8 style $context = (new UnleashContext()) ->setIpAddress($_SERVER['HTTP_X_FORWARDED_FOR']); $enabled = $unleash->isEnabled('some-feature', $context);
用户ID策略
根据用户ID启用功能。用户ID可以是任何字符串。您必须始终通过上下文提供自己的用户ID。
<?php use Rikudou\Unleash\UnleashBuilder; use Rikudou\Unleash\Configuration\UnleashContext; $unleash = UnleashBuilder::create() ->withAppName('Some app name') ->withAppUrl('https://some-app-url.com') ->withInstanceId('Some instance id') ->build(); $context = new UnleashContext(currentUserId: 'some-user-id'); $enabled = $unleash->isEnabled('some-feature', $context);
渐进式发布策略
也称为灵活发布。允许您根据用户的用户ID、会话ID或随机选择仅对部分用户启用功能。默认情况下,按以下顺序尝试:用户ID、会话ID、随机。
如果您在Unleash服务器上指定了用户ID类型,您还必须通过上下文提供用户ID,与用户ID策略相同。会话ID也可以通过上下文提供,默认值为当前会话ID,通过`session_id()`调用。
此策略需要一个粘性计算器,该计算器将ID(用户、会话或随机)转换为1到100之间的数字。您可以提供自己的或使用默认的`\Rikudou\Unleash\Stickiness\MurmurHashCalculator`。
<?php use Rikudou\Unleash\UnleashBuilder; use Rikudou\Unleash\Configuration\UnleashContext; $unleash = UnleashBuilder::create() ->withAppName('Some app name') ->withAppUrl('https://some-app-url.com') ->withInstanceId('Some instance id') ->build(); // assume the feature uses the default type which means that it will default to either session id (if session is started) // or randomly $unleash->isEnabled('some-feature'); // still using the default strategy but this time with user id (which is the first to be used if present) $context = new UnleashContext(currentUserId: 'some-user-id'); $unleash->isEnabled('some-feature', $context); // let's start the session to ensure the session id is used session_start(); $unleash->isEnabled('some-feature'); // or you can provide your own session id $context = new UnleashContext(sessionId: 'sess-123456'); $unleash->isEnabled('some-feature', $context); // assume the feature is set to use the user id, the first call returns false (no context given), the second // one returns true/false based on the user id $unleash->isEnabled('some-feature'); $context = new UnleashContext(currentUserId: 'some-user-id'); $unleash->isEnabled('some-feature', $context); // the same goes for session, assume the session isn't started yet and the feature is set to use the session type $unleash->isEnabled('some-feature'); // returns false because no session is available $context = new UnleashContext(sessionId: 'some-session-id'); $unleash->isEnabled('some-feature', $context); // works because you provided the session id manually session_start(); $unleash->isEnabled('some-feature'); // works because the session is started // lastly you can force the feature to use the random type which always works $unleash->isEnabled('some-feature');
注意:此库还实现了某些弃用的策略,即`gradualRolloutRandom`、`gradualRolloutSessionId`和`gradualRolloutUserId`,这些都代理到渐进式发布策略。
自定义策略
要实现自己的策略,您需要创建一个实现了StrategyHandler
(或包含一些有用方法的AbstractStrategyHandler
)的类。然后,您需要指示构建器使用您的自定义策略。
<?php use Rikudou\Unleash\Strategy\AbstractStrategyHandler; use Rikudou\Unleash\DTO\Strategy; use Rikudou\Unleash\Configuration\Context; use Rikudou\Unleash\Strategy\DefaultStrategyHandler; class AprilFoolsStrategy extends AbstractStrategyHandler { public function __construct(private DefaultStrategyHandler $original) { } public function getStrategyName() : string { return 'aprilFools'; } public function isEnabled(Strategy $strategy, Context $context) : bool { $date = new DateTimeImmutable(); if ((int) $date->format('n') === 4 && (int) $date->format('j') === 1) { return (bool) random_int(0, 1); } return $this->original->isEnabled($strategy, $context); } }
现在您必须指示构建器使用您的新策略
<?php use Rikudou\Unleash\UnleashBuilder; use Rikudou\Unleash\Strategy\IpAddressStrategyHandler; $unleash = UnleashBuilder::create() ->withAppName('Some app name') ->withAppUrl('https://some-app-url.com') ->withInstanceId('Some instance id') ->withStrategy(new AprilFoolsStrategy()) // this will append your strategy to the existing list ->build(); // if you want to replace all strategies, use withStrategies() instead $unleash = UnleashBuilder::create() ->withAppName('Some app name') ->withAppUrl('https://some-app-url.com') ->withInstanceId('Some instance id') ->withStrategies(new AprilFoolsStrategy(), new IpAddressStrategyHandler()) // now the unleash object will have only the two strategies ->build();
变体
您可以使用一个功能的多个变体,例如用于A/B测试。如果没有匹配的变体或该功能没有任何变体,将返回默认的一个,该默认变体对isEnabled()
返回false
。您还可以提供自己的默认变体。
变体可能包含或不包含负载。
<?php use Rikudou\Unleash\DTO\DefaultVariant; use Rikudou\Unleash\UnleashBuilder; use Rikudou\Unleash\Configuration\UnleashContext; use Rikudou\Unleash\Enum\VariantPayloadType; use Rikudou\Unleash\DTO\DefaultVariantPayload; $unleash = UnleashBuilder::create() ->withAppName('Some app name') ->withAppUrl('https://some-app-url.com') ->withInstanceId('Some instance id') ->build(); $variant = $unleash->getVariant('nonexistentFeature'); assert($variant->isEnabled() === false); // getVariant() does isEnabled() call in the background meaning that it will return the default falsy variant // whenever isEnabled() returns false $variant = $unleash->getVariant('existingFeatureThatThisUserDoesNotHaveAccessTo'); assert($variant->isEnabled() === false); $variant = $unleash->getVariant('someFeature', new UnleashContext(currentUserId: '123')); if ($variant->isEnabled()) { $payload = $variant->getPayload(); if ($payload !== null) { if ($payload->getType() === VariantPayloadType::JSON) { $jsonData = $payload->fromJson(); } $stringPayload = $payload->getValue(); } } // providing custom default variant $variant = $unleash->getVariant('nonexistentFeature', fallbackVariant: new DefaultVariant( 'variantName', enabled: true, payload: new DefaultVariantPayload(VariantPayloadType::STRING, 'somePayload'), )); assert($variant->getPayload()->getValue() === 'somePayload');
客户端注册
默认情况下,库会自动将其自己注册为Unleash服务器中的应用程序。如果您想防止这种情况,请在构建器中使用withAutomaticRegistrationEnabled(false)
。
<?php use Rikudou\Unleash\UnleashBuilder; $unleash = UnleashBuilder::create() ->withAppName('Some App Name') ->withAppUrl('https://somewhere.com') ->withInstanceId('some-instance-id') ->withAutomaticRegistrationEnabled(false) ->build(); // event though the client will not attempt to register, you can still use isEnabled() $unleash->isEnabled('someFeature'); // if you want to register manually $unleash->register(); // you can call the register method multiple times, the Unleash server doesn't mind $unleash->register(); $unleash->register();
指标
默认情况下,此库会发送关于用户是否被授予访问权限的简单统计指标的指标。
当创建的包创建时间超过配置的阈值时,指标将被捆绑并发送一次。默认阈值是30,000毫秒(30秒),这意味着在新的包创建后,它不会在30秒内发送。这并不意味着指标将每30秒发送一次,它仅保证指标不会在30秒内发送。
示例
- 用户访问您的网站并触发此sdk,未发送任何指标
- 五秒后用户访问另一个页面,再次触发此sdk,未发送指标
- 用户在执行任何操作之前等待一分钟,没有其他人访问您的网站
- 一分钟后用户访问另一个页面,指标已发送到Unleash服务器
在上面的示例中,由于没有触发代码的人,指标包是在1分钟和5秒后发送的。
<?php use Rikudou\Unleash\UnleashBuilder; $unleash = UnleashBuilder::create() ->withAppName('Some App Name') ->withAppUrl('https://somewhere.com') ->withInstanceId('some-instance-id') ->withMetricsEnabled(false) // turn off metric sending ->withMetricsEnabled(true) // turn on metric sending ->withMetricsInterval(10_000) // interval in milliseconds (10 seconds) ->build(); // the metric will be collected but not sent immediately $unleash->isEnabled('test'); sleep(10); // now the metrics will get sent $unleash->isEnabled('test');
约束
此SDK支持约束,如果存在,将由Unleash::isEnabled()
正确处理。
GitLab特定
- 在GitLab中,您必须使用提供的实例ID,您不能创建自己的。
- 不需要授权头。
- 您需要指定GitLab环境而不是应用程序名称。
- 为此,您可以在构建器中使用
withGitlabEnvironment()
方法,它是withAppName()
的别名,但更好地传达了意图。
- 为此,您可以在构建器中使用
- GitLab不使用注册系统,您可以将SDK设置为禁用自动注册以节省一个HTTP调用。
- GitLab不读取指标,您可以将SDK设置为禁用发送它们以节省一些HTTP调用。
<?php use Rikudou\Unleash\UnleashBuilder; $gitlabUnleash = UnleashBuilder::createForGitlab() ->withInstanceId('H9sU9yVHVAiWFiLsH2Mo') // generated in GitLab ->withAppUrl('https://git.example.com/api/v4/feature_flags/unleash/1') ->withGitlabEnvironment('Production') ->build(); // the above is equivalent to $gitlabUnleash = UnleashBuilder::create() ->withInstanceId('H9sU9yVHVAiWFiLsH2Mo') ->withAppUrl('https://git.example.com/api/v4/feature_flags/unleash/1') ->withGitlabEnvironment('Production') ->withAutomaticRegistrationEnabled(false) ->withMetricsEnabled(false) ->build();