Skip to content

Commit 078eaa3

Browse files
authored
Merge pull request #122 from launchdarkly/eb/sc-177541/re-add-user
2 parents ba5947c + a4fea61 commit 078eaa3

File tree

9 files changed

+870
-32
lines changed

9 files changed

+870
-32
lines changed

Makefile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@ TEMP_TEST_OUTPUT=/tmp/sse-contract-test-service.log
1111

1212
# TEST_HARNESS_PARAMS can be set to add -skip parameters for any contract tests that cannot yet pass
1313
# Explanation of current skips:
14-
# - "evaluation/bucketing/secondary": The "secondary" behavior needs to be removed from contract tests.
1514
# - "evaluation/parameterized/attribute references/array index is not supported": Due to how PHP
1615
# arrays work, there's no way to disallow an array index lookup without breaking object property
1716
# lookups for properties that are numeric strings.
1817
TEST_HARNESS_PARAMS := $(TEST_HARNESS_PARAMS) \
19-
-skip 'evaluation/bucketing/secondary' \
2018
-skip 'evaluation/parameterized/attribute references/array index is not supported'
2119

2220
build-contract-tests:

src/LaunchDarkly/LDClient.php

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,12 @@ private function getFeatureRequester(string $sdkKey, array $options): FeatureReq
178178
* does not match any existing flag), `$defaultValue` is returned.
179179
*
180180
* @param string $key the unique key for the feature flag
181-
* @param LDContext $context the evaluation context
181+
* @param LDContext|LDUser $context the evaluation context or user
182182
* @param mixed $defaultValue the default value of the flag
183-
* @return mixed the variation for the given context, or `$defaultValue` if the flag cannot be evaluated
183+
* @return mixed The variation for the given context, or `$defaultValue` if the flag cannot be evaluated
184184
* @see \LaunchDarkly\LDClient::variationDetail()
185185
*/
186-
public function variation(string $key, LDContext $context, mixed $defaultValue = false): mixed
186+
public function variation(string $key, LDContext|LDUser $context, mixed $defaultValue = false): mixed
187187
{
188188
$detail = $this->variationDetailInternal($key, $context, $defaultValue, $this->_eventFactoryDefault);
189189
return $detail->getValue();
@@ -197,27 +197,28 @@ public function variation(string $key, LDContext $context, mixed $defaultValue =
197197
* detailed event data for this flag.
198198
*
199199
* @param string $key the unique key for the feature flag
200-
* @param LDContext $context the evaluation context
200+
* @param LDContext|LDUser $context the evaluation context or user
201201
* @param mixed $defaultValue the default value of the flag
202202
*
203-
* @return EvaluationDetail an EvaluationDetail object that includes the feature flag value
203+
* @return EvaluationDetail An EvaluationDetail object that includes the feature flag value
204204
* and evaluation reason
205205
*/
206-
public function variationDetail(string $key, LDContext $context, mixed $defaultValue = false): EvaluationDetail
206+
public function variationDetail(string $key, LDContext|LDUser $context, mixed $defaultValue = false): EvaluationDetail
207207
{
208208
return $this->variationDetailInternal($key, $context, $defaultValue, $this->_eventFactoryWithReasons);
209209
}
210210

211211
/**
212212
* @param string $key
213-
* @param LDContext $context
213+
* @param LDContext|LDUser $contextOrUser
214214
* @param mixed $default
215215
* @param EventFactory $eventFactory
216216
*
217217
* @return EvaluationDetail
218218
*/
219-
private function variationDetailInternal(string $key, LDContext $context, mixed $default, EventFactory $eventFactory): EvaluationDetail
219+
private function variationDetailInternal(string $key, LDContext|LDUser $contextOrUser, mixed $default, EventFactory $eventFactory): EvaluationDetail
220220
{
221+
$context = $contextOrUser instanceof LDUser ? LDContext::fromUser($contextOrUser) : $contextOrUser;
221222
$default = $this->_get_default($key, $default);
222223

223224
$errorDetail = fn (string $errorKind): EvaluationDetail =>
@@ -294,17 +295,24 @@ public function isOffline(): bool
294295
}
295296

296297
/**
297-
* Tracks that a user performed an event.
298+
* Tracks that an application-defined event occurred.
299+
*
300+
* This method creates a "custom" analytics event containing the specified event name (key)
301+
* and context properties. You may attach arbitrary data or a metric value to the event with the
302+
* optional `data` and `metricValue` parameters.
303+
*
304+
* Note that event delivery is asynchronous, so the event may not actually be sent until later;
305+
* see {@see \LaunchDarkly\LDClient::flush()}.
298306
*
299307
* @param string $eventName The name of the event
300-
* @param LDContext $context The user that performed the event
308+
* @param LDContext|LDUser $context The evaluation context or user associated with the event
301309
* @param mixed $data Optional additional information to associate with the event
302310
* @param int|float|null $metricValue A numeric value used by the LaunchDarkly experimentation feature in
303-
* numeric custom metrics. Can be omitted if this event is used by only non-numeric metrics. This
304-
* field will also be returned as part of the custom event for Data Export.
311+
* numeric custom metrics; can be omitted if this event is used by only non-numeric metrics
305312
*/
306-
public function track(string $eventName, LDContext $context, mixed $data = null, int|float|null $metricValue = null): void
313+
public function track(string $eventName, LDContext|LDUser $context, mixed $data = null, int|float|null $metricValue = null): void
307314
{
315+
$context = $context instanceof LDUser ? LDContext::fromUser($context) : $context;
308316
if (!$context->isValid()) {
309317
$this->_logger->warning("Track called with null/empty user key!");
310318
return;
@@ -313,16 +321,22 @@ public function track(string $eventName, LDContext $context, mixed $data = null,
313321
}
314322

315323
/**
316-
* Reports details about a user.
324+
* Reports details about an evaluation context or user.
325+
*
326+
* This method simply creates an analytics event containing the context properties, to
327+
* that LaunchDarkly will know about that context if it does not already.
317328
*
318-
* This simply registers the given user properties with LaunchDarkly without evaluating a feature flag.
319-
* This also happens automatically when you evaluate a flag.
329+
* Evaluating a flag, by calling {@see \LaunchDarkly\LDClient::variation()} or
330+
* {@see \LaunchDarkly\LDClient::variationDetail()} :func:`variation_detail()`, also sends
331+
* the context information to LaunchDarkly (if events are enabled), so you only need to use
332+
* identify() if you want to identify the context without evaluating a flag.
320333
*
321-
* @param LDContext $context The user properties
334+
* @param LDContext|LDUser $context The context or user to register
322335
* @return void
323336
*/
324-
public function identify(LDContext $context): void
337+
public function identify(LDContext|LDUser $context): void
325338
{
339+
$context = $context instanceof LDUser ? LDContext::fromUser($context) : $context;
326340
if (!$context->isValid()) {
327341
$this->_logger->warning("Identify called with null/empty user key!");
328342
return;
@@ -339,7 +353,7 @@ public function identify(LDContext $context): void
339353
*
340354
* This method does not send analytics events back to LaunchDarkly.
341355
*
342-
* @param LDContext $context the evalation context
356+
* @param LDContext|LDUser $context the evalation context or user
343357
* @param array $options Optional properties affecting how the state is computed:
344358
* - `clientSideOnly`: Set this to true to specify that only flags marked for client-side use
345359
* should be included; by default, all flags are included
@@ -351,8 +365,9 @@ public function identify(LDContext $context): void
351365
*
352366
* @return FeatureFlagsState a FeatureFlagsState object (will never be null)
353367
*/
354-
public function allFlagsState(LDContext $context, array $options = []): FeatureFlagsState
368+
public function allFlagsState(LDContext|LDUser $context, array $options = []): FeatureFlagsState
355369
{
370+
$context = $context instanceof LDUser ? LDContext::fromUser($context) : $context;
356371
if (!$context->isValid()) {
357372
$error = $context->getError();
358373
$this->_logger->warning("Invalid context for allFlagsState ($error); returning empty state");
@@ -395,11 +410,12 @@ public function allFlagsState(LDContext $context, array $options = []): FeatureF
395410
*
396411
* See: [Secure mode](https://docs.launchdarkly.com/sdk/features/secure-mode)
397412
*
398-
* @param LDContext $context the evaluation context
399-
* @return string the hash value
413+
* @param LDContext|LDUser $context The evaluation context or user
414+
* @return string The hash value
400415
*/
401-
public function secureModeHash(LDContext $context): string
416+
public function secureModeHash(LDContext|LDUser $context): string
402417
{
418+
$context = $context instanceof LDUser ? LDContext::fromUser($context) : $context;
403419
if (!$context->isValid()) {
404420
return "";
405421
}

src/LaunchDarkly/LDContext.php

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,33 @@
66

77
use LaunchDarkly\Types\AttributeReference;
88

9+
function isAllowableUserCustomAttr(string $name): bool
10+
{
11+
switch ($name) {
12+
case 'anonymous':
13+
case 'avatar':
14+
case 'country':
15+
case 'email':
16+
case 'firstName':
17+
case 'ip':
18+
case 'key':
19+
case 'kind':
20+
case 'lastName':
21+
case 'name':
22+
return false;
23+
default:
24+
return true;
25+
}
26+
}
27+
928
/**
1029
* A collection of attributes that can be referenced in flag evaluations and analytics events.
1130
* This entity is also called an "evaluation context."
1231
*
32+
* LDContext is the newer replacement for the previous, less flexible {@see \LaunchDarkly\LDUser} type.
33+
* The current SDK still supports LDUser, but LDContext is now the preferred model and may entirely
34+
* replace User in the future.
35+
*
1336
* To create an LDContext of a single kind, such as a user, you may use
1437
* {@see \LaunchDarkly\LDContext::create()} when only the key and the kind are relevant; or, to
1538
* specify other attributes, use {@see \LaunchDarkly\LDContext::builder()}.
@@ -197,6 +220,60 @@ public static function createMulti(LDContext ...$contexts): LDContext
197220
return $b->build();
198221
}
199222

223+
/**
224+
* @param LDUser $user
225+
* @return LDContext
226+
*/
227+
public static function fromUser(LDUser $user): LDContext
228+
{
229+
$attrs = null;
230+
self::maybeAddAttr($attrs, "avatar", $user->getAvatar());
231+
self::maybeAddAttr($attrs, "country", $user->getCountry());
232+
self::maybeAddAttr($attrs, "email", $user->getEmail());
233+
self::maybeAddAttr($attrs, "firstName", $user->getFirstName());
234+
self::maybeAddAttr($attrs, "ip", $user->getIP());
235+
self::maybeAddAttr($attrs, "lastName", $user->getLastName());
236+
$userCustom = $user->getCustom();
237+
if ($userCustom !== null && count($userCustom) !== 0) {
238+
if ($attrs === null) {
239+
$attrs = [];
240+
}
241+
foreach ($userCustom as $k => $v) {
242+
if (isAllowableUserCustomAttr($k)) {
243+
$attrs[$k] = $v;
244+
}
245+
}
246+
}
247+
$privateAttrs = null;
248+
$userPrivate = $user->getPrivateAttributeNames();
249+
if ($userPrivate !== null && count($userPrivate) !== 0) {
250+
$privateAttrs = [];
251+
foreach ($userPrivate as $pa) {
252+
$privateAttrs[] = AttributeReference::fromLiteral($pa);
253+
}
254+
}
255+
return new LDContext(
256+
self::DEFAULT_KIND,
257+
$user->getKey(),
258+
$user->getName(),
259+
$user->getAnonymous() ?? false,
260+
$attrs,
261+
$privateAttrs,
262+
null,
263+
null
264+
);
265+
}
266+
267+
private static function maybeAddAttr(?array &$attrsOut, string $name, ?string $value): void
268+
{
269+
if ($value !== null) {
270+
if ($attrsOut === null) {
271+
$attrsOut = [];
272+
}
273+
$attrsOut[$name] = $value;
274+
}
275+
}
276+
200277
/**
201278
* Creates a builder for building an LDContext.
202279
*
@@ -754,9 +831,6 @@ private static function decodeJsonSingleKind(array $o, ?string $kind): LDContext
754831

755832
private static function decodeJsonOldUser(array $o, bool $wasParsedAsArray): LDContext
756833
{
757-
$j = json_encode($o);
758-
file_put_contents('php://stderr', "decodeJsonOldUser($j, $wasParsedAsArray)\n", FILE_APPEND);
759-
760834
$b = self::builder('');
761835
$key = null;
762836
foreach ($o as $k => $v) {
@@ -770,7 +844,9 @@ private static function decodeJsonOldUser(array $o, bool $wasParsedAsArray): LDC
770844
throw self::parsingBadTypeError($k);
771845
}
772846
foreach ((array)$v as $k1 => $v1) {
773-
$b->set($k1, $v1);
847+
if (isAllowableUserCustomAttr($k1)) {
848+
$b->set($k1, $v1);
849+
}
774850
}
775851
}
776852
break;

src/LaunchDarkly/LDContextBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public function key(string $key): LDContextBuilder
9797
/**
9898
* Sets the context's kind attribute.
9999
*
100-
* Every LDContext has a kind. Setting it to an empty string or null is equivalent to
100+
* Every context has a kind. Setting it to an empty string or null is equivalent to
101101
* {@see \LaunchDarkly\LDContext::DEFAULT_KIND} ("user"). This value is case-sensitive.
102102
*
103103
* The meaning of the context kind is completely up to the application. Validation rules are

0 commit comments

Comments
 (0)