Skip to content

Proposal: add optional cache for feature flags #43

@drjayvee

Description

@drjayvee

I'm trialing PostHog for our SaaS app. For us, the lack of caching for feature flags are a total no-go. (We're currently canary-releasing a feature that requires a decide on every single page view.)

So I've added caching by extending the PostHog\Client and overriding fetchFeatureVariants.

<?php

use PostHog\Client;
use PostHog\HttpClient;
use Psr\Cache\CacheItemPoolInterface;

class CachingClient extends Client {
	
	private const DEFAULT_EXPIRY_DURATION = 'PT5M';    // five minutes
	
	private ?CacheItemPoolInterface $cache;
	private \DateInterval $cacheExpiryInterval;
	
	public function __construct(
		string $apiKey,
		array $options = [],
		?HttpClient $httpClient = null,
		string $personalAPIKey = null,
		CacheItemPoolInterface $cache = null
	) {
		parent::__construct($apiKey, $options, $httpClient, $personalAPIKey);
		
		$this->cache = $cache;
		$this->cacheExpiryInterval = new \DateInterval($options['cacheDuration'] ?? self::DEFAULT_EXPIRY_DURATION);
	}
	
	public function fetchFeatureVariants(string $distinctId, array $groups = [], array $personProperties = [], array $groupProperties = []): array {
		$callParent = fn() => parent::fetchFeatureVariants($distinctId, $groups, $personProperties, $groupProperties);
		
		if (!$this->cache) {
			return $callParent();
		}
		
		$query = substr(md5(json_encode([$groups, $personProperties, $groupProperties])), 0, 8);	// yes, md5 is totally suitable here: it's fast and provides a good distribution
		$cacheItem = $this->cache->getItem("FeatureFlags.dId=$distinctId.q=$query");
		
		if ($cacheItem->isHit()) {
			return $cacheItem->get();
		}
		
		$result = $callParent();
		
		$this->cache->save(
			$cacheItem
				->expiresAfter($this->cacheExpiryInterval)
				->set($result)
		);
		
		return $result;
	}
	
}

I'd be happy to create a PR to add this to the base class.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions