Skip to content

Commit 5d6aa9f

Browse files
committed
Merge pull request #1 from launchdarkly/jko/event-recording
Record events with PHP
2 parents a06f916 + 84c3966 commit 5d6aa9f

File tree

3 files changed

+212
-2
lines changed

3 files changed

+212
-2
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
namespace LaunchDarkly;
3+
4+
/**
5+
* @internal
6+
*/
7+
class EventProcessor {
8+
9+
private $_apiKey;
10+
private $_queue;
11+
private $_capacity;
12+
private $_timeout;
13+
private $_socket_failed;
14+
private $_host;
15+
private $_port;
16+
private $_ssl;
17+
18+
public function __construct($apiKey, $options = []) {
19+
$this->_apiKey = $apiKey;
20+
if (!isset($options['base_uri'])) {
21+
$this->_host = 'app.launchdarkly.com';
22+
$this->_port = 443;
23+
$this->_ssl = true;
24+
}
25+
else {
26+
$url = parse_url($options['base_uri']);
27+
$this->_host = $url['host'];
28+
$this->_ssl = $url['scheme'] === 'https';
29+
if (isset($url['port'])) {
30+
$this->_port = $url['port'];
31+
}
32+
else {
33+
$this->_port = $this->_ssl ? 443 : 80;
34+
}
35+
}
36+
37+
$this->_capacity = $options['capacity'];
38+
$this->_timeout = $options['timeout'];
39+
40+
$this->_queue = array();
41+
}
42+
43+
public function __destruct() {
44+
$this->flush();
45+
}
46+
47+
public function sendEvent($event) {
48+
return $this->enqueue($event);
49+
}
50+
51+
public function enqueue($event) {
52+
if (count($this->_queue) > $this->_capacity) {
53+
return false;
54+
}
55+
56+
array_push($this->_queue, $event);
57+
58+
return true;
59+
}
60+
61+
protected function flush() {
62+
$socket = $this->createSocket();
63+
64+
if (!$socket) {
65+
error_log("LaunchDarkly unable to open socket");
66+
return;
67+
}
68+
$payload = json_encode($this->_queue);
69+
70+
$body = $this->createBody($payload);
71+
72+
return $this->makeRequest($socket, $body);
73+
}
74+
75+
private function createSocket() {
76+
if ($this->_socket_failed) {
77+
return false;
78+
}
79+
80+
$protocol = $this->_ssl ? "ssl" : "tcp";
81+
82+
try {
83+
84+
$socket = @pfsockopen($protocol . "://" . $this->_host, $this->_port, $errno, $errstr, $this->_timeout);
85+
86+
if ($errno != 0) {
87+
$this->_socket_failed = true;
88+
return false;
89+
}
90+
91+
return $socket;
92+
} catch (Exception $e) {
93+
error_log("LaunchDarkly caught $e");
94+
$this->socket_failed = true;
95+
return false;
96+
}
97+
}
98+
99+
private function createBody($content) {
100+
$req = "";
101+
$req.= "POST /api/events/bulk HTTP/1.1\r\n";
102+
$req.= "Host: " . $this->_host . "\r\n";
103+
$req.= "Content-Type: application/json\r\n";
104+
$req.= "Authorization: api_key " . $this->_apiKey . "\r\n";
105+
$req.= "User-Agent: PHPClient/" . LDClient::VERSION . "\r\n";
106+
$req.= "Accept: application/json\r\n";
107+
$req.= "Content-length: " . strlen($content) . "\r\n";
108+
$req.= "\r\n";
109+
$req.= $content;
110+
return $req;
111+
}
112+
113+
private function makeRequest($socket, $req, $retry = true) {
114+
$bytes_written = 0;
115+
$bytes_total = strlen($req);
116+
$closed = false;
117+
118+
while (!$closed && $bytes_written < $bytes_total) {
119+
try {
120+
$written = @fwrite($socket, substr($req, $bytes_written));
121+
} catch (Exception $e) {
122+
error_log("LaunchDarkly caught $e");
123+
$closed = true;
124+
}
125+
if (!isset($written) || !$written) {
126+
$closed = true;
127+
} else {
128+
$bytes_written += $written;
129+
}
130+
}
131+
132+
if ($closed) {
133+
fclose($socket);
134+
if ($retry) {
135+
error_log("LaunchDarkly retrying send");
136+
$socket = $this->createSocket();
137+
if ($socket) return $this->makeRequest($socket, $req, false);
138+
}
139+
return false;
140+
}
141+
142+
return true;
143+
}
144+
145+
146+
}

src/LaunchDarkly/LDClient.php

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class LDClient {
1414
protected $_apiKey;
1515
protected $_baseUri;
1616
protected $_client;
17+
protected $_eventProcessor;
1718

1819
/**
1920
* Creates a new client instance that connects to LaunchDarkly.
@@ -29,7 +30,8 @@ public function __construct($apiKey, $options = []) {
2930
$this->_apiKey = $apiKey;
3031
if (!isset($options['base_uri'])) {
3132
$this->_baseUri = self::DEFAULT_BASE_URI;
32-
} else {
33+
}
34+
else {
3335
$this->_baseUri = rtrim($options['base_uri'], '/');
3436
}
3537
if (!isset($options['timeout'])) {
@@ -39,6 +41,12 @@ public function __construct($apiKey, $options = []) {
3941
$options['connect_timeout'] = 3;
4042
}
4143

44+
if (!isset($options['capacity'])) {
45+
$options['capacity'] = 1000;
46+
}
47+
48+
$this->_eventProcessor = new \LaunchDarkly\EventProcessor($apiKey, $options);
49+
4250
$this->_client = $this->_make_client($options);
4351
}
4452

@@ -54,13 +62,51 @@ public function __construct($apiKey, $options = []) {
5462
public function getFlag($key, $user, $default = false) {
5563
try {
5664
$flag = $this->_getFlag($key, $user, $default);
57-
return is_null($flag) ? $default : $flag;
65+
66+
if (is_null($flag)) {
67+
$this->_sendFlagRequestEvent($key, $user, $default);
68+
return $default;
69+
}
70+
else {
71+
$this->_sendFlagRequestEvent($key, $user, $flag);
72+
return $flag;
73+
}
5874
} catch (Exception $e) {
5975
error_log("LaunchDarkly caught $e");
76+
$this->_sendFlagRequestEvent($key, $user, $default);
6077
return $default;
6178
}
6279
}
6380

81+
/**
82+
* Tracks that a user performed an event.
83+
*
84+
* @param string $eventName The name of the event
85+
* @param LDUser $user The user that performed the event
86+
*
87+
*/
88+
public function sendEvent($eventName, $user, $data) {
89+
$event = array();
90+
$event['user'] = $user->toJSON();
91+
$event['kind'] = "custom";
92+
$event['creationDate'] = round(microtime(1) * 1000);
93+
$event['key'] = $eventName;
94+
if (isset($data)) {
95+
$event['data'] = $data;
96+
}
97+
$this->_eventProcessor->enqueue($event);
98+
}
99+
100+
protected function _sendFlagRequestEvent($key, $user, $value) {
101+
$event = array();
102+
$event['user'] = $user->toJSON();
103+
$event['value'] = $value;
104+
$event['kind'] = "feature";
105+
$event['creationDate'] = round(microtime(1) * 1000);
106+
$event['key'] = $key;
107+
$this->_eventProcessor->enqueue($event);
108+
}
109+
64110
protected function _getFlag($key, $user, $default) {
65111
try {
66112
$response = $this->_client->get("/api/eval/features/$key");

src/LaunchDarkly/LDUser.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,22 @@ public function getKey() {
4747
public function getSecondary() {
4848
return $this->_secondary;
4949
}
50+
51+
public function toJSON() {
52+
$json = ["key" => $this->_key];
53+
54+
if (isset($this->_secondary)) {
55+
$json['secondary'] = $this->_secondary;
56+
}
57+
if (isset($this->ip)) {
58+
$json['ip'] = $this->ip;
59+
}
60+
if (isset($this->country)) {
61+
$json['country'] = $this->country;
62+
}
63+
if (isset($this->custom)) {
64+
$json['custom'] = $this->custom;
65+
}
66+
return $json;
67+
}
5068
}

0 commit comments

Comments
 (0)