Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions lib/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PostHog\Consumer\File;
use PostHog\Consumer\ForkCurl;
use PostHog\Consumer\LibCurl;
use PostHog\Consumer\NoOp;
use PostHog\Consumer\Socket;
use Symfony\Component\Clock\Clock;

Expand All @@ -18,6 +19,7 @@ class Client
"file" => File::class,
"fork_curl" => ForkCurl::class,
"lib_curl" => LibCurl::class,
"noop" => NoOp::class,
];


Expand Down Expand Up @@ -88,30 +90,37 @@ class Client
*/
private $options;

/**
* @var bool
*/
private $enabled;

/**
* Create a new posthog object with your app's API key
* key
*
* @param string $apiKey
* @param string|null $apiKey
* @param array $options array of consumer options [optional]
* @param HttpClient|null $httpClient
*/
public function __construct(
string $apiKey,
?string $apiKey = null,
array $options = [],
?HttpClient $httpClient = null,
?string $personalAPIKey = null,
bool $loadFeatureFlags = true,
) {
$this->apiKey = trim($apiKey);
$this->apiKey = trim($apiKey ?? '');
$this->enabled = $this->apiKey !== '';
$this->personalAPIKey = StringNormalizer::normalizeOptional($personalAPIKey);
$this->options = $options;
$this->debug = $options["debug"] ?? false;
$this->options['host'] = StringNormalizer::normalizeHost($options['host'] ?? null);
if ($this->apiKey === '') {
if (!$this->enabled) {
error_log('[PostHog][Client] apiKey is empty after trimming whitespace; check your project API key');
$this->options['consumer'] = 'noop';
}
$Consumer = self::CONSUMERS[$options["consumer"] ?? "lib_curl"];
$Consumer = self::CONSUMERS[$this->options["consumer"] ?? "lib_curl"];
$this->consumer = new $Consumer($this->apiKey, $this->options, $httpClient);
$this->httpClient = $httpClient !== null ? $httpClient : new HttpClient(
$this->options['host'],
Expand All @@ -134,7 +143,8 @@ public function __construct(

// Populate featureflags and grouptypemapping if possible
if (
count($this->featureFlags) == 0
$this->enabled
&& count($this->featureFlags) == 0
&& !is_null($this->personalAPIKey)
&& $loadFeatureFlags
) {
Expand Down Expand Up @@ -718,6 +728,17 @@ public function loadFlags()

public function localFlags(): HttpResponse
{
if (!$this->enabled) {
return new HttpResponse(
json_encode([
'flags' => [],
'group_type_mapping' => [],
'cohorts' => [],
]),
200
);
}

$headers = [
// Send user agent in the form of {library_name}/{library_version} as per RFC 7231.
"User-Agent: posthog-php/" . PostHog::VERSION,
Expand Down Expand Up @@ -823,6 +844,14 @@ public function flags(
array $personProperties = [],
array $groupProperties = []
): array {
if (!$this->enabled) {
return [
'featureFlags' => [],
'featureFlagPayloads' => [],
'flags' => [],
];
}

$payload = array(
'api_key' => $this->apiKey,
'distinct_id' => $distinctId,
Expand Down
79 changes: 79 additions & 0 deletions lib/Consumer/NoOp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace PostHog\Consumer;

use PostHog\Consumer;

class NoOp extends Consumer
{
protected $type = "NoOp";

/**
* Define getter method for consumer type
*
* @return string
*/
public function getConsumer()
{
return $this->type;
}

public function __destruct()
{
// No queued work to flush.
}

/**
* Captures a user action
*
* @param array $message
* @return boolean whether the capture call succeeded
*/
public function capture(array $message)
{
return true;
}

/**
* Tags properties about the user.
*
* @param array $message
* @return boolean whether the identify call succeeded
*/
public function identify(array $message)
{
return true;
}

/**
* Aliases from one user id to another
*
* @param array $message
* @return boolean whether the alias call succeeded
*/
public function alias(array $message)
{
return true;
}

/**
* Queue a raw message.
*
* @param mixed $item
* @return boolean whether call has succeeded
*/
public function enqueue($item)
{
return true;
}

/**
* Flush queued messages.
*
* @return boolean true if flushed successfully
*/
public function flush()
{
return true;
}
}
7 changes: 5 additions & 2 deletions lib/PostHog.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ public static function init(
?string $personalAPIKey = null
): void {
if (null === $client) {
$apiKey = $apiKey ?: getenv(self::ENV_API_KEY);
$options = $options ?? [];
if ($apiKey === null) {
$envApiKey = getenv(self::ENV_API_KEY);
$apiKey = $envApiKey === false ? null : $envApiKey;
}

$rawHost = null;
if (array_key_exists("host", $options)) {
Expand All @@ -50,7 +54,6 @@ public static function init(
}
}

self::assert($apiKey, "PostHog::init() requires an apiKey");
self::$client = new Client($apiKey, $options, null, $personalAPIKey);
} else {
self::$client = $client;
Expand Down
85 changes: 81 additions & 4 deletions test/PostHogTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Exception;
use PHPUnit\Framework\TestCase;
use PostHog\Client;
use PostHog\Consumer\NoOp;
use PostHog\PostHog;
use PostHog\Test\Assets\MockedResponses;

Expand Down Expand Up @@ -46,6 +47,14 @@ public function checkEmptyErrorLogs(): void
$this->assertEmpty($errorMessages);
}

private function getConsumer(Client $client): object
{
$ref = new \ReflectionClass($client);
$consumerProp = $ref->getProperty('consumer');

return $consumerProp->getValue($client);
}

public function testInitWithParamApiKey(): void
{
$this->expectNotToPerformAssertions();
Expand Down Expand Up @@ -204,11 +213,79 @@ public function testInitWithEnvHttpHostSetsSslFalse(): void
putenv(PostHog::ENV_HOST);
}

public function testInitThrowsExceptionWithNoApiKey(): void
public function testInitWithoutApiKeyConfiguresNoOpClient(): void
{
$previousApiKey = getenv(PostHog::ENV_API_KEY);
putenv(PostHog::ENV_API_KEY);

try {
PostHog::init();
$client = PostHog::getClient();

$this->assertInstanceOf(NoOp::class, $this->getConsumer($client));
$this->assertTrue(PostHog::capture([
"distinctId" => "john",
"event" => "Module PHP Event",
]));
} finally {
if ($previousApiKey === false) {
putenv(PostHog::ENV_API_KEY);
} else {
putenv(PostHog::ENV_API_KEY . "=" . $previousApiKey);
}
}
}

public function testInitWithEmptyApiKeyConfiguresNoOpClient(): void
{
PostHog::init("", ["debug" => true]);
$client = PostHog::getClient();

$this->assertInstanceOf(NoOp::class, $this->getConsumer($client));
$this->assertTrue(PostHog::capture([
"distinctId" => "john",
"event" => "Module PHP Event",
]));
}

public function testInitWithWhitespaceApiKeyConfiguresNoOpClient(): void
{
PostHog::init(" \n\t ", ["debug" => true]);
$client = PostHog::getClient();

$this->assertInstanceOf(NoOp::class, $this->getConsumer($client));
$this->assertTrue(PostHog::capture([
"distinctId" => "john",
"event" => "Module PHP Event",
]));
}

public function testClientWithNullApiKeyDoesNotSendRequests(): void
{
$this->expectException(Exception::class);
$this->expectExceptionMessage("PostHog::init() requires an apiKey");
PostHog::init(null);
$httpClient = new MockedHttpClient("app.posthog.com");
$client = new Client(null, ["debug" => true, "batch_size" => 1], $httpClient);

$this->assertInstanceOf(NoOp::class, $this->getConsumer($client));
$this->assertTrue($client->capture([
"distinctId" => "john",
"event" => "Module PHP Event",
]));
$this->assertSame([], $client->getAllFlags("john"));
$this->assertSame([], $httpClient->calls ?? []);
}

public function testClientWithTrimEmptyApiKeyDoesNotSendRequests(): void
{
$httpClient = new MockedHttpClient("app.posthog.com");
$client = new Client(" \n\t ", ["debug" => true, "batch_size" => 1], $httpClient);

$this->assertInstanceOf(NoOp::class, $this->getConsumer($client));
$this->assertTrue($client->capture([
"distinctId" => "john",
"event" => "Module PHP Event",
]));
$this->assertSame([], $client->fetchFeatureVariants("john"));
$this->assertSame([], $httpClient->calls ?? []);
}

public function testCapture(): void
Expand Down
Loading