Skip to content

Commit c4a0eb5

Browse files
authored
Release version 2.2.0
* Support publishing events via ld-relay * Add documentation for GuzzleEventPublisher and CurlEventPublisher
1 parent 4e7022a commit c4a0eb5

12 files changed

+235
-75
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

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

5+
## [2.2.0] - 2017-06-05
6+
### Added
7+
- Support for [publishing events via ld-relay](README.md#using-ld-relay)
8+
- Allow `EventPublisher` to be injected into the client.
9+
- `GuzzleEventPublisher` as a synchronous, in-process alternative to publishing events via background processes.
10+
511
## [2.1.2] - 2017-04-27
612
### Changed
713
- Relaxed the requirement on `kevinrob/guzzle-cache-middleware` for the default `GuzzleFeatureRequester`.

README.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,23 +58,40 @@ Require Guzzle as a dependency:
5858

5959
It will then be used as the default way of fetching flags.
6060

61-
With Guzzle, you could persist your cache somewhere other than the default in-memory store, like Memcached or Redis. You could then specify your cache when initializing the client with the [cache option](https://github.com/launchdarkly/php-client/blob/master/src/LaunchDarkly/LDClient.php#L42).
61+
With Guzzle, you could persist your cache somewhere other than the default in-memory store, like Memcached or Redis. You could then specify your cache when initializing the client with the [cache option](https://github.com/launchdarkly/php-client/blob/master/src/LaunchDarkly/LDClient.php#L44).
6262

6363
$client = new LaunchDarkly\LDClient("YOUR_SDK_KEY", array("cache" => $cacheStorage));
6464

6565

6666
Using LD-Relay
6767
==============
6868

69-
* Setup [ld-relay](https://github.com/launchdarkly/ld-relay) in [daemon-mode](https://github.com/launchdarkly/ld-relay#redis-storage-and-daemon-mode) with Redis
69+
The LaunchDarkly Relay Proxy ([ld-relay](https://github.com/launchdarkly/ld-relay)) consumes the LaunchDarkly streaming API and can update
70+
a Redis cache operating in your production environment. The ld-relay offers many benefits such as performance and feature flag consistency. With PHP applications, we strongly recommend setting up ld-relay with a Redis store.
7071

71-
* Require Predis as a dependency:
72+
1. Set up ld-relay in [daemon-mode](https://github.com/launchdarkly/ld-relay#redis-storage-and-daemon-mode) with Redis
7273

73-
php composer.phar require "predis/predis:1.0.*"
74+
2. Require Predis as a dependency:
7475

75-
* Create the LDClient with the Redis feature requester as an option:
76+
php composer.phar require "predis/predis:1.0.*"
7677

77-
$client = new LaunchDarkly\LDClient("your_sdk_key", ['feature_requester_class' => 'LaunchDarkly\LDDFeatureRequester', 'redis_host' => 'your.redis.host', 'redis_port' => 6379]);
78+
3. Create the LDClient with the Redis feature requester as an option:
79+
80+
$client = new LaunchDarkly\LDClient("your_sdk_key", [
81+
'feature_requester_class' => 'LaunchDarkly\LDDFeatureRequester',
82+
'redis_host' => 'your.redis.host',
83+
'redis_port' => 6379
84+
]);
85+
86+
4. If ld-relay is configured for [event forwarding](https://github.com/launchdarkly/ld-relay#event-forwarding), you can configure the LDClient to publish events to ld-relay instead of directly to `events.launchdarkly.com`. Using `GuzzleEventPublisher` with ld-relay event forwarding can be an efficient alternative to the default `curl`-based event publishing.
87+
88+
$client = new LaunchDarkly\LDClient("your_sdk_key", [
89+
'event_publisher_class' => 'LaunchDarkly\GuzzleEventPublisher',
90+
'events_uri' => 'http://your-ldrelay-host:8030',
91+
'feature_requester_class' => 'LaunchDarkly\LDDFeatureRequester',
92+
'redis_host' => 'your.redis.host',
93+
'redis_port' => 6379
94+
]);
7895

7996
Testing
8097
-------

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.1.2
1+
2.2.0

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"zendframework/zend-serializer": "^2.7"
2828
},
2929
"suggest": {
30-
"guzzlehttp/guzzle": "(^6.2.1) Required when using the default FeatureRequester",
30+
"guzzlehttp/guzzle": "(^6.2.1) Required when using GuzzleEventPublisher or the default FeatureRequester",
3131
"kevinrob/guzzle-cache-middleware": "(^1.4.1) Recommended for performance when using the default FeatureRequester",
3232
"predis/predis": "(^1.0) Required when using LDDFeatureRequester"
3333
},

src/LaunchDarkly/ApcLDDFeatureRequester.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
class ApcLDDFeatureRequester extends LDDFeatureRequester {
1414
protected $_expiration = 30;
1515

16-
function __construct($baseUri, $apiKey, $options) {
17-
parent::__construct($baseUri, $apiKey, $options);
16+
function __construct($baseUri, $sdkKey, $options) {
17+
parent::__construct($baseUri, $sdkKey, $options);
1818

1919
if (isset($options['apc_expiration'])) {
2020
$this->_expiration = (int)$options['apc_expiration'];
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
namespace LaunchDarkly;
3+
4+
/**
5+
* Sends events to the LaunchDarkly service using the `curl` command line tool.
6+
* The `curl` requests are executed as background processes in order to
7+
* minimize overhead to the PHP request. This `EventPublisher` implementation
8+
* is the default for `LDClient`.
9+
*
10+
* `curl` must be installed in the environment's search path, or otherwise the
11+
* absolute path to the executable must be specified using the `'curl'` option
12+
* for `LDClient`.
13+
*/
14+
class CurlEventPublisher implements EventPublisher
15+
{
16+
private $_sdkKey;
17+
private $_host;
18+
private $_port;
19+
private $_ssl;
20+
private $_curl = '/usr/bin/env curl';
21+
22+
function __construct($sdkKey, array $options = array()) {
23+
$this->_sdkKey = $sdkKey;
24+
25+
$eventsUri = LDClient::DEFAULT_EVENTS_URI;
26+
if (isset($options['events_uri'])) {
27+
$eventsUri = $options['events_uri'];
28+
}
29+
$url = parse_url(rtrim($eventsUri,'/'));
30+
$this->_host = $url['host'];
31+
$this->_ssl = $url['scheme'] === 'https';
32+
if (isset($url['port'])) {
33+
$this->_port = $url['port'];
34+
}
35+
else {
36+
$this->_port = $this->_ssl ? 443 : 80;
37+
}
38+
if (isset($url['path'])) {
39+
$this->_path = $url['path'];
40+
}
41+
else {
42+
$this->_path = '';
43+
}
44+
45+
if (array_key_exists('curl', $options)) {
46+
$this->_curl = $options['curl'];
47+
}
48+
}
49+
50+
public function publish($payload) {
51+
$args = $this->createArgs($payload);
52+
53+
return $this->makeRequest($args);
54+
}
55+
56+
private function createArgs($payload) {
57+
$scheme = $this->_ssl ? "https://" : "http://";
58+
$args = " -X POST";
59+
$args.= " -H 'Content-Type: application/json'";
60+
$args.= " -H " . escapeshellarg("Authorization: " . $this->_sdkKey);
61+
$args.= " -H 'User-Agent: PHPClient/" . LDClient::VERSION . "'";
62+
$args.= " -H 'Accept: application/json'";
63+
$args.= " -d " . escapeshellarg($payload);
64+
$args.= " " . escapeshellarg($scheme . $this->_host . ":" . $this->_port . $this->_path . "/bulk");
65+
return $args;
66+
}
67+
68+
private function makeRequest($args) {
69+
$cmd = $this->_curl . " " . $args . ">> /dev/null 2>&1 &";
70+
shell_exec($cmd);
71+
return true;
72+
}
73+
}

src/LaunchDarkly/EventProcessor.php

Lines changed: 25 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,13 @@
66
*/
77
class EventProcessor {
88

9-
private $_sdkKey;
9+
private $_eventPublisher;
1010
private $_queue;
1111
private $_capacity;
1212
private $_timeout;
13-
private $_host;
14-
private $_port;
15-
private $_ssl;
16-
private $_curl = '/usr/bin/env curl';
17-
18-
public function __construct($apiKey, $options = array()) {
19-
$this->_sdkKey = $apiKey;
20-
if (!isset($options['events_uri'])) {
21-
$this->_host = 'events.launchdarkly.com';
22-
$this->_port = 443;
23-
$this->_ssl = true;
24-
$this->_path = '';
25-
}
26-
else {
27-
$url = parse_url(rtrim($options['events_uri'],'/'));
28-
$this->_host = $url['host'];
29-
$this->_ssl = $url['scheme'] === 'https';
30-
if (isset($url['port'])) {
31-
$this->_port = $url['port'];
32-
}
33-
else {
34-
$this->_port = $this->_ssl ? 443 : 80;
35-
}
36-
if (isset($url['path'])) {
37-
$this->_path = $url['path'];
38-
}
39-
else {
40-
$this->_path = '';
41-
}
42-
}
43-
44-
if (array_key_exists('curl', $options)) {
45-
$this->_curl = $options['curl'];
46-
}
13+
14+
public function __construct($sdkKey, $options = array()) {
15+
$this->_eventPublisher = $this->getEventPublisher($sdkKey, $options);
4716

4817
$this->_capacity = $options['capacity'];
4918
$this->_timeout = $options['timeout'];
@@ -76,28 +45,29 @@ protected function flush() {
7645

7746
$payload = json_encode($this->_queue);
7847

79-
$args = $this->createArgs($payload);
80-
81-
return $this->makeRequest($args);
82-
}
83-
84-
private function createArgs($payload) {
85-
$scheme = $this->_ssl ? "https://" : "http://";
86-
$args = " -X POST";
87-
$args.= " -H 'Content-Type: application/json'";
88-
$args.= " -H " . escapeshellarg("Authorization: " . $this->_sdkKey);
89-
$args.= " -H 'User-Agent: PHPClient/" . LDClient::VERSION . "'";
90-
$args.= " -H 'Accept: application/json'";
91-
$args.= " -d " . escapeshellarg($payload);
92-
$args.= " " . escapeshellarg($scheme . $this->_host . ":" . $this->_port . $this->_path . "/bulk");
93-
return $args;
48+
return $this->_eventPublisher->publish($payload);
9449
}
9550

96-
private function makeRequest($args) {
97-
$cmd = $this->_curl . " " . $args . ">> /dev/null 2>&1 &";
98-
shell_exec($cmd);
99-
return true;
100-
}
51+
/**
52+
* @param string $sdkKey
53+
* @param mixed[] $options
54+
* @return EventPublisher
55+
*/
56+
private function getEventPublisher($sdkKey, array $options)
57+
{
58+
if (isset($options['event_publisher']) && $options['event_publisher'] instanceof EventPublisher) {
59+
return $options['event_publisher'];
60+
}
10161

62+
if (isset($options['event_publisher_class'])) {
63+
$eventPublisherClass = $options['event_publisher_class'];
64+
} else {
65+
$eventPublisherClass = CurlEventPublisher::class;
66+
}
10267

68+
if (!is_a($eventPublisherClass, EventPublisher::class, true)) {
69+
throw new \InvalidArgumentException;
70+
}
71+
return new $eventPublisherClass($sdkKey, $options);
72+
}
10373
}

src/LaunchDarkly/EventPublisher.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
namespace LaunchDarkly;
3+
4+
/**
5+
* Provides a transport mechanism for sending events to the LaunchDarkly service.
6+
*/
7+
interface EventPublisher {
8+
/**
9+
* @param string $sdkKey The SDK key for your account
10+
* @param mixed[] $options Client configuration settings
11+
*/
12+
public function __construct($sdkKey, array $options);
13+
14+
/**
15+
* Publish events to LaunchDarkly
16+
*
17+
* @param $payload string Event payload
18+
* @return bool Whether the events were successfully published
19+
*/
20+
public function publish($payload);
21+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
namespace LaunchDarkly;
3+
4+
use GuzzleHttp\Client;
5+
use Psr\Log\LoggerInterface;
6+
7+
/**
8+
* Sends events to the LaunchDarkly service using the GuzzleHttp client.
9+
* This `EventPublisher` implement provides an in-process alternative to
10+
* the default `CurlEventPublisher` implementation which forks processes.
11+
*
12+
* Note that this implementation executes synchronously in the request
13+
* handler. In order to minimize request overhead, we recommend that you
14+
* set up `ld-relay` in your production environment and configure the
15+
* `events_uri` option for `LDClient` to publish to `ld-relay`.
16+
*/
17+
class GuzzleEventPublisher implements EventPublisher
18+
{
19+
/** @var string */
20+
private $_sdkKey;
21+
/** @var string */
22+
private $_eventsUri;
23+
/** @var LoggerInterface */
24+
private $_logger;
25+
/** @var mixed[] */
26+
private $_requestOptions;
27+
28+
function __construct($sdkKey, array $options = array()) {
29+
$this->_sdkKey = $sdkKey;
30+
$this->_logger = $options['logger'];
31+
if (isset($options['events_uri'])) {
32+
$this->_eventsUri = $options['events_uri'];
33+
} else {
34+
$this->_eventsUri = LDClient::DEFAULT_EVENTS_URI;
35+
}
36+
$this->_requestOptions = [
37+
'headers' => [
38+
'Content-Type' => 'application/json',
39+
'Authorization' => $this->_sdkKey,
40+
'User-Agent' => 'PHPClient/' . LDClient::VERSION,
41+
'Accept' => 'application/json'
42+
],
43+
'timeout' => $options['timeout'],
44+
'connect_timeout' => $options['connect_timeout']
45+
];
46+
}
47+
48+
public function publish($payload) {
49+
$client = new Client(['base_uri' => $this->_eventsUri]);
50+
51+
try {
52+
$options = $this->_requestOptions;
53+
$options['body'] = $payload;
54+
$response = $client->request('POST', '/bulk', $options);
55+
56+
return $response->getStatusCode() < 300;
57+
} catch (\Exception $e) {
58+
$this->_logger->warning("GuzzleEventPublisher::publish caught $e");
59+
return false;
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)