Skip to content

Commit 22324e8

Browse files
authored
Merge pull request #13 from eplusminus/php-5.3
update PHP 5.3 branch again
2 parents 78d2dd2 + 6f0411a commit 22324e8

File tree

11 files changed

+538
-79
lines changed

11 files changed

+538
-79
lines changed

src/LaunchDarkly/EventProcessor.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
class EventProcessor {
88

99
private $_eventPublisher;
10+
private $_eventSerializer;
1011
private $_queue;
1112
private $_capacity;
1213
private $_timeout;
1314

1415
public function __construct($sdkKey, $options = array()) {
1516
$this->_eventPublisher = $this->getEventPublisher($sdkKey, $options);
16-
17+
$this->_eventSerializer = new EventSerializer($options);
18+
1719
$this->_capacity = $options['capacity'];
1820
$this->_timeout = $options['timeout'];
1921

@@ -47,7 +49,10 @@ public function flush() {
4749
return null;
4850
}
4951

50-
$payload = json_encode($this->_queue);
52+
$payload = $this->_eventSerializer->serializeEvents($this->_queue);
53+
54+
// We don't expect flush to be called more than once per request cycle, but let's empty the queue just in case
55+
$this->_queue = array();
5156

5257
$this->_queue = array();
5358

src/LaunchDarkly/EventPublisher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ public function __construct($sdkKey, array $options);
1818
* @return bool Whether the events were successfully published
1919
*/
2020
public function publish($payload);
21-
}
21+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
namespace LaunchDarkly;
3+
4+
/**
5+
* @internal
6+
*/
7+
class EventSerializer {
8+
9+
private $_allAttrsPrivate;
10+
private $_privateAttrNames;
11+
12+
public function __construct($options) {
13+
$this->_allAttrsPrivate = isset($options['all_attributes_private']) && $options['all_attributes_private'];
14+
$this->_privateAttrNames = isset($options['private_attribute_names']) ? $options['private_attribute_names'] : array();
15+
}
16+
17+
public function serializeEvents($events) {
18+
$filtered = array();
19+
foreach ($events as $e) {
20+
array_push($filtered, $this->filterEvent($e));
21+
}
22+
return json_encode($filtered);
23+
}
24+
25+
private function filterEvent($e) {
26+
$ret = array();
27+
foreach ($e as $key => $value) {
28+
if ($key == 'user') {
29+
$ret[$key] = $this->serializeUser($value);
30+
}
31+
else {
32+
$ret[$key] = $value;
33+
}
34+
}
35+
return $ret;
36+
}
37+
38+
private function filterAttrs($attrs, &$json, $userPrivateAttrs, &$allPrivateAttrs) {
39+
foreach ($attrs as $key => $value) {
40+
if ($value != null) {
41+
if ($this->_allAttrsPrivate ||
42+
array_search($key, $userPrivateAttrs) !== FALSE ||
43+
array_search($key, $this->_privateAttrNames) !== FALSE) {
44+
$allPrivateAttrs[$key] = true;
45+
}
46+
else {
47+
$json[$key] = $value;
48+
}
49+
}
50+
}
51+
}
52+
53+
private function serializeUser($user) {
54+
$json = array("key" => $user->getKey());
55+
$userPrivateAttrs = $user->getPrivateAttributeNames();
56+
$allPrivateAttrs = array();
57+
58+
$attrs = array(
59+
'secondary' => $user->getSecondary(),
60+
'ip' => $user->getIP(),
61+
'country' => $user->getCountry(),
62+
'email' => $user->getEmail(),
63+
'name' => $user->getName(),
64+
'avatar' => $user->getAvatar(),
65+
'firstName' => $user->getFirstName(),
66+
'lastName' => $user->getLastName(),
67+
'anonymous' => $user->getAnonymous()
68+
);
69+
$this->filterAttrs($attrs, $json, $userPrivateAttrs, $allPrivateAttrs);
70+
if ($user->getCustom()) {
71+
$customs = array();
72+
$this->filterAttrs($user->getCustom(), $customs, $userPrivateAttrs, $allPrivateAttrs);
73+
$json['custom'] = $customs;
74+
}
75+
if (count($allPrivateAttrs)) {
76+
$pa = array_keys($allPrivateAttrs);
77+
sort($pa);
78+
$json['privateAttrs'] = $pa;
79+
}
80+
return $json;
81+
}
82+
}

src/LaunchDarkly/GuzzleEventPublisher.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,18 @@ function __construct($sdkKey, array $options = array()) {
4949

5050
public function publish($payload) {
5151
$client = new Client($this->_eventsUri);
52+
$response = null;
5253

5354
try {
5455
$req = $client->post('/bulk', $this->_requestHeaders, $payload, $this->_requestOptions);
5556
$response = $req->send();
56-
57-
return $response->getStatusCode() < 300;
5857
} catch (\Exception $e) {
5958
$this->_logger->warning("GuzzleEventPublisher::publish caught $e");
6059
return false;
6160
}
61+
if ($response && ($response->getStatusCode() == 401)) {
62+
throw new InvalidSDKKeyException();
63+
}
64+
return $response && ($response->getStatusCode() < 300);
6265
}
6366
}

src/LaunchDarkly/GuzzleFeatureRequester.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ public function get($key)
5959
return FeatureFlag::decode(json_decode($body, true));
6060
} catch (BadResponseException $e) {
6161
$code = $e->getResponse()->getStatusCode();
62-
$this->_logger->error("GuzzleFeatureRequester::get received an unexpected HTTP status code $code");
62+
if ($code == 404) {
63+
$this->_logger->warning("GuzzleFeatureRequester::get returned 404. Feature flag does not exist for key: " . $key);
64+
} else {
65+
$this->handleUnexpectedStatus($code, "GuzzleFeatureRequester::get");
66+
}
6367
return null;
6468
}
6569
}
@@ -76,9 +80,15 @@ public function getAll() {
7680
$body = $response->getBody();
7781
return array_map(FeatureFlag::getDecoder(), json_decode($body, true));
7882
} catch (BadResponseException $e) {
79-
$code = $e->getResponse()->getStatusCode();
80-
$this->_logger->error("GuzzleFeatureRequester::getAll received an unexpected HTTP status code $code");
83+
$this->handleUnexpectedStatus($e->getResponse()->getStatusCode(), "GuzzleFeatureRequester::getAll");
8184
return null;
8285
}
8386
}
87+
88+
private function handleUnexpectedStatus($code, $method) {
89+
$this->_logger->error("$method received an unexpected HTTP status code $code");
90+
if ($code == 401) {
91+
throw new InvalidSDKKeyException();
92+
}
93+
}
8494
}

src/LaunchDarkly/LDClient.php

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
use Monolog\Logger;
66
use Psr\Log\LoggerInterface;
77

8+
/**
9+
* Used internally.
10+
*/
11+
class InvalidSDKKeyException extends \Exception
12+
{
13+
}
14+
815
/**
916
* A client for the LaunchDarkly API.
1017
*/
@@ -49,6 +56,8 @@ class LDClient {
4956
* - feature_requester_class: An optional class implementing LaunchDarkly\FeatureRequester, if `feature_requester` is not specified. Defaults to GuzzleFeatureRequester.
5057
* - event_publisher: An optional LaunchDarkly\EventPublisher instance.
5158
* - event_publisher_class: An optional class implementing LaunchDarkly\EventPublisher, if `event_publisher` is not specified. Defaults to CurlEventPublisher.
59+
* - all_attributes_private: True if no user attributes (other than the key) should be sent back to LaunchDarkly. By default, this is false.
60+
* - private_attribute_names: An optional array of user attribute names to be marked private. Any users sent to LaunchDarkly with this configuration active will have attributes with these names removed. You can also set private attributes on a per-user basis in LDUserBuilder.
5261
*/
5362
public function __construct($sdkKey, $options = array()) {
5463
$this->_sdkKey = $sdkKey;
@@ -144,7 +153,12 @@ public function variation($key, $user, $default = false) {
144153
if ($user->isKeyBlank()) {
145154
$this->_logger->warning("User key is blank. Flag evaluation will proceed, but the user will not be stored in LaunchDarkly.");
146155
}
147-
$flag = $this->_featureRequester->get($key);
156+
try {
157+
$flag = $this->_featureRequester->get($key);
158+
} catch (InvalidSDKKeyException $e) {
159+
$this->handleInvalidSDKKey();
160+
return $default;
161+
}
148162

149163
if (is_null($flag)) {
150164
$this->_sendFlagRequestEvent($key, $user, $default, $default);
@@ -207,7 +221,7 @@ public function track($eventName, $user, $data) {
207221
}
208222

209223
$event = array();
210-
$event['user'] = $user->toJSON();
224+
$event['user'] = $user;
211225
$event['kind'] = "custom";
212226
$event['creationDate'] = Util::currentTimeUnixMillis();
213227
$event['key'] = $eventName;
@@ -229,7 +243,7 @@ public function identify($user) {
229243
}
230244

231245
$event = array();
232-
$event['user'] = $user->toJSON();
246+
$event['user'] = $user;
233247
$event['kind'] = "identify";
234248
$event['creationDate'] = Util::currentTimeUnixMillis();
235249
$event['key'] = $user->getKey();
@@ -252,7 +266,15 @@ public function allFlags($user) {
252266
$this->_logger->warn("allFlags called with null user or null/empty user key! Returning null");
253267
return null;
254268
}
255-
$flags = $this->_featureRequester->getAll();
269+
if ($this->isOffline()) {
270+
return null;
271+
}
272+
try {
273+
$flags = $this->_featureRequester->getAll();
274+
} catch (InvalidSDKKeyException $e) {
275+
$this->handleInvalidSDKKey();
276+
return null;
277+
}
256278
if ($flags === null) {
257279
return null;
258280
}
@@ -286,7 +308,11 @@ public function secureModeHash($user) {
286308
*/
287309
public function flush()
288310
{
289-
return $this->_eventProcessor->flush();
311+
try {
312+
return $this->_eventProcessor->flush();
313+
} catch (InvalidSDKKeyException $e) {
314+
$this->handleInvalidSDKKey();
315+
}
290316
}
291317

292318
/**
@@ -311,4 +337,9 @@ protected function _get_default($key, $default) {
311337
return $default;
312338
}
313339
}
340+
341+
protected function handleInvalidSDKKey() {
342+
$this->_logger->error("Received 401 error, no further HTTP requests will be made during lifetime of LDClient since SDK key is invalid");
343+
$this->_offline = true;
344+
}
314345
}

src/LaunchDarkly/LDUser.php

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class LDUser {
1818
protected $_lastName = null;
1919
protected $_anonyomus = false;
2020
protected $_custom = array();
21+
protected $_privateAttributeNames = array();
2122

2223
/**
2324
* @param string $key Unique key for the user. For authenticated users, this may be a username or e-mail address. For anonymous users, this could be an IP address or session ID.
@@ -32,7 +33,8 @@ class LDUser {
3233
* @param boolean|null $anonymous Whether this is an anonymous user
3334
* @param array|null $custom Other custom attributes that can be used to create custom rules
3435
*/
35-
public function __construct($key, $secondary = null, $ip = null, $country = null, $email = null, $name = null, $avatar = null, $firstName = null, $lastName = null, $anonymous = null, $custom = array()) {
36+
public function __construct($key, $secondary = null, $ip = null, $country = null, $email = null, $name = null, $avatar = null, $firstName = null, $lastName = null, $anonymous = null, $custom = array(),
37+
$privateAttributeNames = array()) {
3638
if ($key !== null) {
3739
$this->_key = strval($key);
3840
}
@@ -46,6 +48,7 @@ public function __construct($key, $secondary = null, $ip = null, $country = null
4648
$this->_lastName = $lastName;
4749
$this->_anonymous = $anonymous;
4850
$this->_custom = $custom;
51+
$this->_privateAttributeNames = $privateAttributeNames;
4952
}
5053

5154
public function getValueForEvaluation($attr) {
@@ -129,43 +132,11 @@ public function getAnonymous() {
129132
return $this->_anonymous;
130133
}
131134

135+
public function getPrivateAttributeNames() {
136+
return $this->_privateAttributeNames;
137+
}
138+
132139
public function isKeyBlank() {
133140
return isset($this->_key) && empty($this->_key);
134141
}
135-
136-
public function toJSON() {
137-
$json = array("key" => $this->_key);
138-
139-
if (isset($this->_secondary)) {
140-
$json['secondary'] = $this->_secondary;
141-
}
142-
if (isset($this->_ip)) {
143-
$json['ip'] = $this->_ip;
144-
}
145-
if (isset($this->_country)) {
146-
$json['country'] = $this->_country;
147-
}
148-
if (isset($this->_email)) {
149-
$json['email'] = $this->_email;
150-
}
151-
if (isset($this->_name)) {
152-
$json['name'] = $this->_name;
153-
}
154-
if (isset($this->_avatar)) {
155-
$json['avatar'] = $this->_avatar;
156-
}
157-
if (isset($this->_firstName)) {
158-
$json['firstName'] = $this->_firstName;
159-
}
160-
if (isset($this->_lastName)) {
161-
$json['lastName'] = $this->_lastName;
162-
}
163-
if (isset($this->_custom) && !empty($this->_custom)) {
164-
$json['custom'] = $this->_custom;
165-
}
166-
if (isset($this->_anonymous)) {
167-
$json['anonymous'] = $this->_anonymous;
168-
}
169-
return $json;
170-
}
171142
}

0 commit comments

Comments
 (0)