Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to the LaunchDarkly PHP SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).

## [3.3.0] - 2018-08-27
### Added:
- The new `LDClient` method `allFlagsState()` should be used instead of `allFlags()` if you are passing flag data to the front end for use with the JavaScript SDK. It preserves some flag metadata that the front end requires in order to send analytics events correctly. Versions 2.5.0 and above of the JavaScript SDK are able to use this metadata, but the output of `allFlagsState()` will still work with older versions.
- The `allFlagsState()` method also allows you to select only client-side-enabled flags to pass to the front end, by using the option `clientSideOnly => true`.

### Deprecated:
- `LDClient.allFlags()`

## [3.2.1] - 2018-07-16
### Fixed:
- The `LDClient::VERSION` constant has been fixed to report the current version. In the previous release, it was still set to 3.1.0.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.2.1
3.3.0
2 changes: 1 addition & 1 deletion src/LaunchDarkly/EvalResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function __construct($variation, $value, array $prerequisiteEvents)
}

/**
* @return int
* @return int | null
*/
public function getVariation()
{
Expand Down
48 changes: 46 additions & 2 deletions src/LaunchDarkly/FeatureFlag.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ class FeatureFlag
protected $_variations = array();
/** @var bool */
protected $_deleted = false;
/** @var bool */
protected $_trackEvents = false;
/** @var int | null */
protected $_debugEventsUntilDate = null;
/** @var bool */
protected $_clientSide = false;

// Note, trackEvents and debugEventsUntilDate are not used in EventProcessor, because
// the PHP client doesn't do summary events. However, we need to capture them in case
// they want to pass the flag data to the front end with allFlagsState().

protected function __construct($key,
$version,
Expand All @@ -38,7 +48,10 @@ protected function __construct($key,
$fallthrough,
$offVariation,
array $variations,
$deleted)
$deleted,
$trackEvents,
$debugEventsUntilDate,
$clientSide)
{
$this->_key = $key;
$this->_version = $version;
Expand All @@ -51,6 +64,9 @@ protected function __construct($key,
$this->_offVariation = $offVariation;
$this->_variations = $variations;
$this->_deleted = $deleted;
$this->_trackEvents = $trackEvents;
$this->_debugEventsUntilDate = $debugEventsUntilDate;
$this->_clientSide = $clientSide;
}

public static function getDecoder()
Expand All @@ -67,7 +83,11 @@ public static function getDecoder()
call_user_func(VariationOrRollout::getDecoder(), $v['fallthrough']),
$v['offVariation'],
$v['variations'] ?: [],
$v['deleted']);
$v['deleted'],
isset($v['trackEvents']) && $v['trackEvents'],
isset($v['debugEventsUntilDate']) ? $v['debugEventsUntilDate'] : null,
isset($v['clientSide']) && $v['clientSide']
);
};
}

Expand Down Expand Up @@ -222,4 +242,28 @@ public function isDeleted()
{
return $this->_deleted;
}

/**
* @return boolean
*/
public function isTrackEvents()
{
return $this->_trackEvents;
}

/**
* @return int | null
*/
public function getDebugEventsUntilDate()
{
return $this->_debugEventsUntilDate;
}

/**
* @return boolean
*/
public function isClientSide()
{
return $this->_clientSide;
}
}
95 changes: 95 additions & 0 deletions src/LaunchDarkly/FeatureFlagsState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
namespace LaunchDarkly;

/**
* A snapshot of the state of all feature flags with regard to a specific user, generated by
* calling LDClient.allFlagsState(). Serializing this object to JSON using json_encode(), or
* the jsonSerialize() method, will produce the appropriate data structure for bootstrapping
* the LaunchDarkly JavaScript client.
*/
class FeatureFlagsState implements \JsonSerializable
{
/** @var bool */
protected $_valid = false;

/** @var array */
protected $_flagValues;

/** @var array */
protected $_flagMetadata;

public function __construct($valid, $flagValues = array(), $flagMetadata = array())
{
$this->_valid = $valid;
$this->_flagValues = array();
$this->_flagMetadata = array();
}

/**
* Used internally to build the state map.
*/
public function addFlag($flag, $evalResult)
{
$this->_flagValues[$flag->getKey()] = $evalResult->getValue();
$meta = array();
if (!is_null($evalResult->getVariation())) {
$meta['variation'] = $evalResult->getVariation();
}
$meta['version'] = $flag->getVersion();
$meta['trackEvents'] = $flag->isTrackEvents();
if ($flag->getDebugEventsUntilDate()) {
$meta['debugEventsUntilDate'] = $flag->getDebugEventsUntilDate();
}
$this->_flagMetadata[$flag->getKey()] = $meta;
}

/**
* Returns true if this object contains a valid snapshot of feature flag state, or false if the
* state could not be computed (for instance, because the client was offline or there was no user).
* @return bool true if the state is valid
*/
public function isValid()
{
return $this->_valid;
}

/**
* Returns the value of an individual feature flag at the time the state was recorded.
* @param $key string
* @return mixed the flag's value; null if the flag returned the default value, or if there was no such flag
*/
public function getFlagValue($key)
{
return isset($this->_flagValues[$key]) ? $this->_flagValues[$key] : null;
}

/**
* Returns an associative array of flag keys to flag values. If a flag would have evaluated to the default
* value, its value will be null.
* <p>
* Do not use this method if you are passing data to the front end to "bootstrap" the JavaScript client.
* Instead, use jsonSerialize().
* @return array an associative array of flag keys to JSON values
*/
public function toValuesMap()
{
return $this->_flagValues;
}

/**
* Returns a JSON representation of the entire state map (as an associative array), in the format used
* by the LaunchDarkly JavaScript SDK. Use this method if you are passing data to the front end in
* order to "bootstrap" the JavaScript client.
* <p>
* Note that calling json_encode() on a FeatureFlagsState object will automatically use the
* jsonSerialize() method.
* @return array an associative array suitable for passing as a JSON object
*/
public function jsonSerialize()
{
$ret = array_replace([], $this->_flagValues);
$ret['$flagsState'] = $this->_flagMetadata;
$ret['$valid'] = $this->_valid;
return $ret;
}
}
56 changes: 40 additions & 16 deletions src/LaunchDarkly/LDClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class LDClient
{
const DEFAULT_BASE_URI = 'https://app.launchdarkly.com';
const DEFAULT_EVENTS_URI = 'https://events.launchdarkly.com';
const VERSION = '3.2.1';
const VERSION = '3.3.0';

/** @var string */
protected $_sdkKey;
Expand Down Expand Up @@ -260,38 +260,62 @@ public function identify($user)
* This method will not send analytics events back to LaunchDarkly.
* <p>
* The most common use case for this method is to bootstrap a set of client-side feature flags from a back-end service.
*
* @deprecated Use allFlagsState() instead. Current versions of the client-side SDK will not
* generate analytics events correctly if you pass the result of allFlags().
* @param $user LDUser the end user requesting the feature flags
* @return array()|null Mapping of feature flag keys to their evaluated results for $user
*/
public function allFlags($user)
{
if (is_null($user) || is_null($user->getKey())) {
$this->_logger->warn("allFlags called with null user or null/empty user key! Returning null");
$state = $this->allFlagsState($user);
if (!$state->isValid()) {
return null;
}
return $state->toValuesMap();
}

/**
* Returns an object that encapsulates the state of all feature flags for a given user, including the flag
* values and also metadata that can be used on the front end. This method does not send analytics events
* back to LaunchDarkly.
* <p>
* The most common use case for this method is to bootstrap a set of client-side feature flags from a back-end service.
* To convert the state object into a JSON data structure, call its toJson() method.
* @param $user LDUser the end user requesting the feature flags
* @param $options array optional properties affecting how the state is computed; set
* <code>'clientSideOnly' => true</code> to specify that only flags marked for client-side use
* should be included (by default, all flags are included)
* @return FeatureFlagsState a FeatureFlagsState object (will never be null; see FeatureFlagsState.isValid())
*/
public function allFlagsState($user, $options = array())
{
if (is_null($user) || is_null($user->getKey())) {
$this->_logger->warn("allFlagsState called with null user or null/empty user key! Returning empty state");
return new FeatureFlagsState(false);
}
if ($this->isOffline()) {
return null;
return new FeatureFlagsState(false);
}
try {
$flags = $this->_featureRequester->getAllFeatures();
} catch (UnrecoverableHTTPStatusException $e) {
$this->handleUnrecoverableError();
return null;
return new FeatureFlagsState(false);
}
if ($flags === null) {
return null;
return new FeatureFlagsState(false);
}

/**
* @param $flag FeatureFlag
* @return mixed|null
*/
$eval = function ($flag) use ($user) {
return $flag->evaluate($user, $this->_featureRequester)->getValue();
};

return array_map($eval, $flags);
$state = new FeatureFlagsState(true);
$clientOnly = isset($options['clientSideOnly']) && $options['clientSideOnly'];
foreach ($flags as $key => $flag) {
if ($clientOnly && !$flag->isClientSide()) {
continue;
}
$result = $flag->evaluate($user, $this->_featureRequester);
$state->addFlag($flag, $result);
}
return $state;
}

/** Generates an HMAC sha256 hash for use in Secure mode: https://github.com/launchdarkly/js-client#secure-mode
Expand Down
Loading