Skip to content
Open
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
29 changes: 29 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,35 @@ jobs:
- name: Run test suite
run: composer run-script test

# Smoke-test the SDK against Symfony HttpClient as the discovered PSR-18
# implementation, ensuring the discovery + integration path doesn't
# regress on a non-Guzzle stack. Single PHP version is sufficient since
# the SDK doesn't reference symfony types directly.
tests-symfony:
runs-on: ubuntu-latest
name: Tests with Symfony HttpClient (PSR-18)
steps:
- name: Checkout
uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2

- name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2
with:
php-version: '8.3'

- name: Swap PSR-18 implementation to Symfony HttpClient
run: |
composer remove --dev --no-update guzzlehttp/guzzle
composer require --dev --no-update \
"symfony/http-client:^6.0 || ^7.0" \
"nyholm/psr7:^1.0"

- name: Install dependencies
run: composer install --no-interaction --no-progress

- name: Run test suite
run: composer run-script test

static-analysis:
strategy:
fail-fast: false
Expand Down
22 changes: 17 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,32 @@
"license": "MIT",
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"allow-plugins": {
"php-http/discovery": false
}
},
"require": {
"php": "^7.4 || ^8",
"ext-json": "*",
"guzzlehttp/guzzle": "^7",
"php-http/discovery": "^1.15",
"psr/cache": "^1 || ^2 || ^3",
"psr/http-client": "^1.0",
"psr/http-client-implementation": "^1.0",
"psr/http-factory": "^1.0",
"psr/http-factory-implementation": "^1.0",
"psr/log": "^1 || ^2 || ^3",
"symfony/cache": "^5.1.0 || ^6 || ^7 || ^8"
},
"require-dev": {
"phpunit/phpunit": "=9.6.33",
"phpstan/phpstan": "=1.12.32"
"guzzlehttp/guzzle": "^7.4",
"guzzlehttp/psr7": "^2.0",
"phpstan/phpstan": "=1.12.32",
"phpunit/phpunit": "=9.6.33"
},
"suggest": {
"guzzlehttp/guzzle": "A widely-used PSR-18 HTTP client; auto-discovered when installed.",
"symfony/http-client": "Symfony's PSR-18 HTTP client; auto-discovered when installed.",
"monolog/monolog": "A PSR-3 logger to receive SDK log output; pass it via the logger config option. The SDK defaults to Psr\\Log\\NullLogger."
},
"autoload": {
Expand All @@ -43,8 +56,7 @@
"src/EvaluationCore/Util.php",
"src/Flag/Util.php",
"src/Assignment/AssignmentConstants.php",
"src/Exposure/ExposureConstants.php",
"src/Http/GuzzleConstants.php"
"src/Exposure/ExposureConstants.php"
]
},
"autoload-dev": {
Expand Down
23 changes: 15 additions & 8 deletions src/Amplitude/Amplitude.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace AmplitudeExperiment\Amplitude;

use AmplitudeExperiment\Http\HttpClientInterface;
use AmplitudeExperiment\Http\GuzzleHttpClient;
use AmplitudeExperiment\Http\HttpClientFactory;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

Expand All @@ -21,7 +23,9 @@ class Amplitude
* @var array<array<string,mixed>>
*/
protected array $queue = [];
protected HttpClientInterface $httpClient;
protected ClientInterface $httpClient;
protected RequestFactoryInterface $requestFactory;
protected StreamFactoryInterface $streamFactory;
private LoggerInterface $logger;
private AmplitudeConfig $config;

Expand All @@ -30,7 +34,11 @@ public function __construct(string $apiKey, ?AmplitudeConfig $config = null)
$this->apiKey = $apiKey;
$this->config = $config ?? AmplitudeConfig::builder()->build();
$this->logger = $this->config->logger ?? new NullLogger();
$this->httpClient = $this->config->httpClient ?? new GuzzleHttpClient($this->config->guzzleClientConfig);
[$this->httpClient, $this->requestFactory, $this->streamFactory] = HttpClientFactory::resolveAll(
$this->config->httpClient,
$this->config->requestFactory,
$this->config->retryConfig
);
}

public function flush(): void
Expand Down Expand Up @@ -67,7 +75,6 @@ public function __destruct()
*/
private function post(string $url, array $payload): void
{
$httpClient = $this->httpClient->getClient();
$payloadJson = json_encode($payload);

if ($payloadJson === false) {
Expand All @@ -77,12 +84,12 @@ private function post(string $url, array $payload): void

$payloadString = $this->payloadToString($payload);

$request = $this->httpClient
->createRequest('POST', $url, $payloadJson)
$request = $this->requestFactory->createRequest('POST', $url)
->withBody($this->streamFactory->createStream($payloadJson))
->withHeader('Content-Type', 'application/json');

try {
$response = $httpClient->sendRequest($request);
$response = $this->httpClient->sendRequest($request);
if ($response->getStatusCode() != 200) {
$this->logger->error('[Amplitude] Failed to send event: ' . $payloadString . ', ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
return;
Expand Down
51 changes: 31 additions & 20 deletions src/Amplitude/AmplitudeConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use AmplitudeExperiment\Assignment\AssignmentConfig;
use AmplitudeExperiment\Assignment\AssignmentConfigBuilder;
use AmplitudeExperiment\Http\HttpClientInterface;
use AmplitudeExperiment\Http\RetryConfig;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Log\LoggerInterface;

/**
Expand Down Expand Up @@ -35,14 +37,23 @@ class AmplitudeConfig
*/
public bool $useBatch;
/**
* The underlying HTTP client to use for requests, if this is not set, the default {@link GuzzleHttpClient} will be used.
* The PSR-18 HTTP client to use for requests. If null, a PSR-18
* implementation is auto-discovered via php-http/discovery and wrapped
* in {@link \AmplitudeExperiment\Http\RetryingClient} using
* {@link $retryConfig}. A user-supplied client is used verbatim with
* no retry wrap.
*/
public ?HttpClientInterface $httpClient;
public ?ClientInterface $httpClient;
/**
* @var array<string, mixed>
* The configuration for the underlying default {@link GuzzleHttpClient} client (if used). See {@link GUZZLE_DEFAULTS} for defaults.
* The PSR-17 request factory used to construct requests. If null, a
* PSR-17 factory is auto-discovered.
*/
public array $guzzleClientConfig;
public ?RequestFactoryInterface $requestFactory;
/**
* Retry configuration for the auto-wrapped client. Ignored when
* {@link $httpClient} is supplied — the user's client is used verbatim.
*/
public ?RetryConfig $retryConfig;
/**
* Set to use a custom PSR-3 logger. If not set, a {@link \Psr\Log\NullLogger} is used
* and SDK log messages are discarded. Pass any PSR-3 implementation (e.g. Monolog, or
Expand All @@ -66,30 +77,30 @@ class AmplitudeConfig
'minIdLength' => 5,
'flushQueueSize' => 200,
'httpClient' => null,
'guzzleClientConfig' => [],
'requestFactory' => null,
'retryConfig' => null,
'logger' => null,
];

/**
* @param array<string, mixed> $guzzleClientConfig
*/
public function __construct(
int $flushQueueSize,
int $minIdLength,
string $serverZone,
string $serverUrl,
bool $useBatch,
?HttpClientInterface $httpClient,
array $guzzleClientConfig,
?LoggerInterface $logger)
{
int $flushQueueSize,
int $minIdLength,
string $serverZone,
string $serverUrl,
bool $useBatch,
?ClientInterface $httpClient,
?RequestFactoryInterface $requestFactory,
?RetryConfig $retryConfig,
?LoggerInterface $logger
) {
$this->flushQueueSize = $flushQueueSize;
$this->minIdLength = $minIdLength;
$this->serverZone = $serverZone;
$this->serverUrl = $serverUrl;
$this->useBatch = $useBatch;
$this->httpClient = $httpClient;
$this->guzzleClientConfig = $guzzleClientConfig;
$this->requestFactory = $requestFactory;
$this->retryConfig = $retryConfig;
$this->logger = $logger;
}

Expand Down
38 changes: 27 additions & 11 deletions src/Amplitude/AmplitudeConfigBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace AmplitudeExperiment\Amplitude;

use AmplitudeExperiment\Http\HttpClientInterface;
use AmplitudeExperiment\Http\RetryConfig;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Log\LoggerInterface;

class AmplitudeConfigBuilder
Expand All @@ -12,11 +14,9 @@ class AmplitudeConfigBuilder
protected string $serverZone = AmplitudeConfig::DEFAULTS['serverZone'];
protected ?string $serverUrl = null;
protected bool $useBatch = AmplitudeConfig::DEFAULTS['useBatch'];
protected ?HttpClientInterface $httpClient = AmplitudeConfig::DEFAULTS['httpClient'];
/**
* @var array<string, mixed>
*/
protected array $guzzleClientConfig = AmplitudeConfig::DEFAULTS['guzzleClientConfig'];
protected ?ClientInterface $httpClient = AmplitudeConfig::DEFAULTS['httpClient'];
protected ?RequestFactoryInterface $requestFactory = AmplitudeConfig::DEFAULTS['requestFactory'];
protected ?RetryConfig $retryConfig = AmplitudeConfig::DEFAULTS['retryConfig'];
protected ?LoggerInterface $logger = AmplitudeConfig::DEFAULTS['logger'];

public function __construct()
Expand Down Expand Up @@ -53,18 +53,33 @@ public function useBatch(bool $useBatch): AmplitudeConfigBuilder
return $this;
}

public function httpClient(HttpClientInterface $httpClient): AmplitudeConfigBuilder
/**
* Supply a PSR-18 HTTP client. The SDK uses it verbatim — no retry wrap.
* If omitted, a client is auto-discovered and wrapped in
* {@link \AmplitudeExperiment\Http\RetryingClient} using {@link retryConfig}.
*/
public function httpClient(ClientInterface $httpClient): AmplitudeConfigBuilder
{
$this->httpClient = $httpClient;
return $this;
}

/**
* @param array<string, mixed> $guzzleClientConfig
* Supply a PSR-17 request factory. If omitted, a factory is auto-discovered.
*/
public function requestFactory(RequestFactoryInterface $requestFactory): AmplitudeConfigBuilder
{
$this->requestFactory = $requestFactory;
return $this;
}

/**
* Configure retry behavior for the auto-discovered client. Ignored when
* a client is supplied via {@link httpClient()}.
*/
public function guzzleClientConfig(array $guzzleClientConfig): AmplitudeConfigBuilder
public function retryConfig(RetryConfig $retryConfig): AmplitudeConfigBuilder
{
$this->guzzleClientConfig = $guzzleClientConfig;
$this->retryConfig = $retryConfig;
return $this;
}

Expand All @@ -90,7 +105,8 @@ public function build(): AmplitudeConfig
$this->serverUrl,
$this->useBatch,
$this->httpClient,
$this->guzzleClientConfig,
$this->requestFactory,
$this->retryConfig,
$this->logger
);
}
Expand Down
48 changes: 0 additions & 48 deletions src/Backoff.php

This file was deleted.

16 changes: 16 additions & 0 deletions src/Exception/MissingHttpImplementationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace AmplitudeExperiment\Exception;

use LogicException;

/**
* Raised when the SDK cannot discover a PSR-18 client or PSR-17 factory and
* the consumer did not supply one via the config builder. Always indicates
* a configuration / installation gap, not a transient failure — extends
* {@link LogicException} so consumers can handle it distinctly from
* runtime transport faults.
*/
class MissingHttpImplementationException extends LogicException
{
}
Loading
Loading