Commit 689e6e22 authored by Brian Hatchet's avatar Brian Hatchet :speech_balloon:

Now with caching and live feature flags.

The only thing really left to be unleash client compliant is the actual activation strategy implementation.

Using the zend cache which implements the PSR-16 interface is a huge time saver and the unleash client can be constructed with any SimpleCache interface compliant mechanism.
1 merge request!1WIP: Epic/unleash client
Pipeline #93152187 passed with stage
in 1 minute and 17 seconds
......@@ -20,7 +20,10 @@
"require": {
"php": ">=7.3",
"guzzlehttp/guzzle": "^6.3@dev",
"monolog/monolog": "^2.0@dev"
"monolog/monolog": "^2.0@dev",
"psr/simple-cache": "^1.0@dev",
"zendframework/zend-cache": "^2.9@dev",
"zendframework/zend-serializer": "^2.9@dev"
},
"require-dev": {
"bossa/phpspec2-expect": "^3.0",
......@@ -34,7 +37,8 @@
],
"psr-4": {
"Minds\\UnleashClient\\": "src/",
"Minds\\UnleashClient\\Entities\\": "src/Entities"
"Minds\\UnleashClient\\Entities\\": "src/Entities/",
"Minds\\UnleashClient\\Cache\\": "src/Cache/"
}
},
"scripts": {
......
This diff is collapsed.
......@@ -11,9 +11,9 @@ use Psr\Http\Message\ResponseInterface;
class ClientSpec extends ObjectBehavior
{
private const TEST_URL = "TEST URL";
private const TEST_APPLICATION_NAME = "TEST APPLICATION NAME";
private const TEST_INSTANCE_ID = "TEST INSTANCE ID";
private const TEST_URL = "test_url";
private const TEST_APPLICATION_NAME = "test_application_name";
private const TEST_INSTANCE_ID = "test_instance_id";
private const TEST_POLLING_INTERVAL_SECONDS = 15;
private const TEST_METRICS_INTERVAL_SECONDS = 20;
/** @var Config; */
......
<?php
namespace spec\Minds\UnleashClient\Entities;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Config;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ContextSpec extends ObjectBehavior
{
private const TEST_APP_NAME = "test_app_name";
private const TEST_USER_ID = "test_user_id";
private const TEST_SESSION_ID = "test_session_id";
private const TEST_REMOTE_ADDRESS = "test_remote_address";
private const TEST_ENVIRONMENT = "test_environment";
/** @var Config */
private $config;
public function let(Config $config)
{
$this->config = $config;
$this->config->getApplicationName()
->willReturn(ContextSpec::TEST_APP_NAME);
$this->beConstructedWith(
$this->config,
ContextSpec::TEST_USER_ID,
ContextSpec::TEST_SESSION_ID,
ContextSpec::TEST_REMOTE_ADDRESS,
ContextSpec::TEST_ENVIRONMENT
);
}
public function it_is_initializable()
{
$this->shouldHaveType(Context::class);
}
}
......@@ -7,6 +7,7 @@ use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Minds\UnleashClient\Config;
use Minds\UnleashClient\Client;
use Zend\Cache\Psr\SimpleCache\SimpleCacheDecorator as Cache;
class UnleashSpec extends ObjectBehavior
{
......@@ -15,11 +16,11 @@ class UnleashSpec extends ObjectBehavior
/** @var Client */
private $client;
public function let(Config $config, Client $client)
public function let(Config $config, Client $client, Cache $cache)
{
$this->config = $config;
$this->client = $client;
$this->beConstructedWith($config, $client);
$this->beConstructedWith($config, $client, $cache);
}
public function it_is_initializable()
......
......@@ -17,6 +17,9 @@ function main() : void
$unleash->register();
$logger->debug('Getting available feature flags');
$features = $unleash->getFeatureFlags();
$logger->debug('Checking enabled');
$enabled = $unleash->isEnabled('test', false);
$logger->info("'test' flag evaluates to {$enabled}");
}
main();
......@@ -88,6 +88,10 @@ class Client
return $features;
}
/**
* Sends
*/
/**
* Creates an http client with the auth headers
* and middleware
......
<?php
namespace Minds\UnleashClient\Entities;
use Minds\UnleashClient\Config;
/**
* Context data object for Unleash api calls
* Send when the client checks feature flags
*/
class Context
{
/** @var string */
private $userId;
/** @var string */
private $sessionId;
/** @var string */
private $remoteAddress;
/** @var string */
private $appName;
/** @var string */
private $environment;
public function __construct(
Config $config,
string $userId = null,
string $sessionId = null,
string $remoteAddress = null,
string $environment = null
) {
$this->appName = $config->getApplicationName();
$this->userId = $userId ?? "";
$this->sessionId = $sessionId ?? "";
$this->remoteAddress = $remoteAddress ?? "";
$this->environment = $environment ?? "";
}
/**
* Get the value of environment
*
* @return string
*/
public function getEnvironment() : string
{
return $this->environment;
}
/**
* Set the value of environment
*
* @return string
*/
public function setEnvironment(string $environment) : Context
{
$this->environment = $environment;
return $this;
}
/**
* Get the value of userId
*
* @return string
*/
public function getUserId() : string
{
return $this->userId;
}
/**
* Set the value of userId
*
* @return Context
*/
public function setUserId($userId) : Context
{
$this->userId = $userId;
return $this;
}
/**
* Get the value of sessionId
*
* @
*/
public function getSessionId() : string
{
return $this->sessionId;
}
/**
* Set the value of sessionId
*
* @return self
*/
public function setSessionId($sessionId) : Context
{
$this->sessionId = $sessionId;
return $this;
}
/**
* Get the value of remoteAddress
* @return string
*/
public function getRemoteAddress() : string
{
return $this->remoteAddress;
}
/**
* Set the value of remoteAddress
*
* @return Contextg
*/
public function setRemoteAddress($remoteAddress) : Context
{
$this->remoteAddress = $remoteAddress;
return $this;
}
/**
* Get the value of appName
*/
public function getAppName() : string
{
return $this->appName;
}
/**
* Set the value of appName
*
* @return Context
*/
public function setAppName($appName) : Context
{
$this->appName = $appName;
return $this;
}
}
......@@ -4,6 +4,11 @@ namespace Minds\UnleashClient;
use Minds\UnleashClient\Config;
use Minds\UnleashClient\Client;
use Psr\SimpleCache\CacheInterface;
use Zend\Cache\StorageFactory;
use Zend\Cache\Psr\SimpleCache\SimpleCacheDecorator;
use Zend\Cache\Storage\Adapter\Filesystem;
use Minds\UnleashClient\Logger;
class Unleash
{
......@@ -11,12 +16,17 @@ class Unleash
private $config;
/** @var Client */
private $client;
/** @var CacheInterface */
private $cache;
/** @var Logger */
private $logger;
public function __construct(Config $config = null, Client $client = null)
public function __construct(Config $config = null, Client $client = null, CacheInterface $cache = null)
{
$this->logger = new Logger();
$this->config = $config ?: new Config();
$this->client = $client ?: new Client($this->config);
$this->cache = $cache ?: $this->buildSimpleCache();
}
/**
......@@ -25,7 +35,23 @@ class Unleash
*/
public function isEnabled(string $featureFlag, bool $default = false) : bool
{
return $default;
try {
$this->logger->debug('checking cache');
if (!$this->cache->has($featureFlag)) {
$this->logger->debug("{$featureFlag} not found in cache, refreshing");
$featureFlags = $this->getFeatureFlags();
foreach ($featureFlags as $feature) {
$this->cache->set($feature->getName(), $feature);
}
} else {
$this->logger->debug("{$featureFlag} cached!");
}
$flag = $this->cache->get($featureFlag, null);
return $flag->isEnabled() ?? $default;
} catch (\Exception $ex) {
$this->logger->error('Error getting feature flags');
$this->logger->error($ex->getMessage());
}
}
/**
......@@ -46,4 +72,19 @@ class Unleash
{
return $this->client->register();
}
private function buildSimpleCache() : CacheInterface
{
$this->logger->debug('building cache');
$storage = StorageFactory::factory([
'adapter' => [
'name' => 'filesystem',
'options' => ''
],
'plugins' => ['serializer']
]);
$plugin = StorageFactory::pluginFactory('serializer');
$storage->addPlugin($plugin);
return new SimpleCacheDecorator($storage);
}
}
Please register or to comment