Repository navigation
fix: audit operator fixes and PHP 8.4 compat #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
joalves
wants to merge
46
commits into
main
Choose a base branch
from
fix/audit-operator-fixes
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
43c2c4c
fix: cross-SDK test compatibility fixes
joalves cc89d5a
fix: PHP 8.4 deprecation and test expectations
joalves f4a2515
fix: queue exposure when variables exist regardless of key
joalves d88e1f2
fix: use correct traffic seed property names for eligibility check
joalves ecabe41
feat: add comprehensive test coverage improvements
joalves 9168988
docs: add Laravel, Symfony examples and async patterns to PHP SDK
joalves ab7c04f
fix: correct HTTPClient class name in PHP SDK docs
joalves 4b575fd
test: add canonical test parity (169 → 336 tests)
joalves 0d51ff6
fix: audit fixes for operator correctness and async interface support
joalves e01fbbe
docs: restructure README to match standard SDK documentation structure
joalves d82b09e
feat: add createSimple() with correct parameter ordering
joalves 235ab4a
docs: update README documentation
joalves b8fa6f9
feat(php-sdk): add getCustomFieldKeys, getCustomFieldValueType, ready…
joalves 1f04e03
feat(php-sdk): add isFinalized/finalize aliases and standardize error…
joalves eb7aa19
fix: read methods return safe defaults instead of throwing when not r…
joalves 9933447
fix(php-sdk): standardize error messages in Context
joalves e88af05
fix: update setUnit exception messages to use double quotes and corre…
joalves e9e6bba
feat: cross-SDK consistency fixes — all 201 scenarios passing
joalves 1ebd865
refactor: rename ContextEventHandler to ContextPublisher
joalves ed9cc97
fix: address coderabbit review issues
joalves 5b685c2
feat: add setContextPublisher() method, deprecate setContextEventHand…
joalves 0978673
fix(jsonexpr): restore haystack-first IN operator (CONTAINS semantics)
joalves 622be80
fix: type Experiment.customFieldValues as ?array
joalves 72279c2
fix(jsonexpr): skip binary operators on null operands
joalves 528e9b1
fix: read customFieldValues as canonical array of {name,type,value}
joalves a0a41a7
test: exclude live-network integration suite from default phpunit run
joalves 63c76ca
test: add hermetic local-HTTP integration test for real fetch/publish
joalves 7cbb092
fix: restore pending events and propagate error on publish failure
joalves 7a191fe
fix: address review feedback (react deps, async error handling, publi…
joalves 7542186
fix: allow react/async ^3 for PHP 7.4/8.0 and drop non-existent promi…
joalves 1a81a83
fix: keep exposures publishable after failed refresh, PHP<8.1 murmur …
joalves fbee828
fix: SDK::createWithDefaults returns SDK; make async client tests ass…
joalves c4f1972
fix: select AsyncContextDataProvider by default when the HTTP client …
joalves d136bd1
docs: remove unsupported retries option from async HTTP client setup
joalves 9c37e4d
feat: retry transient failures in ReactHttpClient (also fix missing H…
joalves bc7bfc4
fix: make ReactHttpClient retries cancellable
joalves 5606cbc
feat!: JS SDK behavioral parity (maintainer decisions D1-D8)
joalves b83efef
fix: keep numeric-key JSON objects equal in deep comparison
joalves ab65e08
fix: keep empty lists distinct from objects in deep comparison
joalves 634f8bd
ci: trigger CI after re-enabling the workflow
joalves 2086356
test: start local integration server portably on Windows
joalves b5dcab9
fix: align units, goal validation, failed loads and failed finalize w…
joalves b2d1b9a
fix: hash numeric units exactly like JavaScript toFixed(0); treat equ…
joalves 83b005f
fix: distinct integer units always conflict, even beyond float precision
joalves 9abb2dd
fix: format large numeric units independently of serialize_precision
joalves 0c368c2
Address historical review cleanup
joalves File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,3 +5,8 @@ composer.lock | |
| coverage.xml | ||
| .phpunit.cache | ||
| phpunit.xml | ||
|
|
||
| .claude/ | ||
| .DS_Store | ||
| AUDIT_REPORT.md | ||
| FIXES_IMPLEMENTED.md | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # Changelog | ||
|
|
||
| ## 2.0.0 (unreleased) | ||
|
|
||
| ### BREAKING | ||
|
|
||
| - **Units keep their type.** `setUnit`/`setUnits` (Context and ContextConfig) accept strings or numbers and | ||
| `getUnit`/`getUnits` return the value as given (old: numbers were cast to strings). A float unit is hashed as | ||
| its value rounded to an integer, like the JavaScript SDK. String UIDs are trimmed. | ||
| Unit error messages now match the other SDKs: `Unit '<type>' UID already set.`, | ||
| `Unit '<type>' UID must not be blank.`, `Unit '<type>' must be a string or a number.` | ||
| - **`track()` validates goal properties.** Anything other than null, a plain object or an associative array | ||
| raises `Goal '<name>' properties must be of type object.` (old: PHP `TypeError` or silently accepted). | ||
| - **A failed context load no longer rejects.** `createContextAsync()`/`createContextPending()` resolve with a | ||
| context that is ready and failed, has empty data, stores `readyError()` and logs an `Error` event. | ||
| - **`close()` rethrows a failed final publish.** The events stay queued and the context stays open so `close()` | ||
| can be retried (old: the error was swallowed and the context closed). | ||
| - New `ABSmartly\SDK\Utils` helpers (`hashUnit`, `base64UrlNoPadding`, `utf8Bytes`, `isObject`, `isNumeric`, | ||
| `isPromise`). | ||
|
|
||
| - **Context reads throw before ready and after finalize** (parity with the JavaScript SDK `_checkReady`). | ||
| The following methods now throw `ABSmartly\SDK\Exception\LogicException`: | ||
| `getTreatment`, `peekTreatment`, `getVariableValue`, `peekVariableValue`, `getVariableKeys`, | ||
| `getExperiments`, `getCustomFieldKeys`, `customFieldValue`, `getCustomFieldValueType`. | ||
| - Before ready (e.g. a context from `createContextPending()` whose data has not arrived): | ||
| message `ABsmartly Context is not yet ready.` | ||
| Old: `peekVariableValue`/`getVariableKeys`/`getExperiments` returned defaults or empty arrays. | ||
| - After `close()`/`finalize()`: message `ABsmartly Context is finalized.` | ||
| Old: `peekVariableValue` and `getVariableKeys` kept returning values. | ||
| `getExperiments()` is still allowed after finalize (as in JS). | ||
| - The not-ready message changed from `ABSmartly Context is not yet ready` to | ||
| `ABsmartly Context is not yet ready.` Code matching on the message text must be updated. |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| <?php | ||
|
|
||
| namespace ABSmartly\SDK; | ||
|
|
||
| use ABSmartly\SDK\Client\Client; | ||
| use ABSmartly\SDK\Client\ClientConfig; | ||
| use ABSmartly\SDK\Context\AsyncContextDataProvider; | ||
| use ABSmartly\SDK\Context\Context; | ||
| use ABSmartly\SDK\Context\ContextConfig; | ||
| use ABSmartly\SDK\Context\ContextData; | ||
| use ABSmartly\SDK\Context\ContextDataProvider; | ||
| use ABSmartly\SDK\Context\ContextPublisher; | ||
| use ABSmartly\SDK\Context\ContextEventLogger; | ||
| use ABSmartly\SDK\Http\HTTPClient; | ||
| use React\Promise\PromiseInterface; | ||
|
|
||
| use function React\Promise\reject; | ||
| use function React\Promise\resolve; | ||
|
|
||
| class ABsmartly { | ||
|
|
||
| private Client $client; | ||
| private ContextPublisher $handler; | ||
| private ContextDataProvider $provider; | ||
| private ?ContextEventLogger $eventLogger; | ||
|
|
||
| public function __construct(Config $config) { | ||
| $this->client = $config->getClient(); | ||
| $this->provider = $config->getContextDataProvider(); | ||
| $this->handler = $config->getContextEventHandler(); | ||
| $this->eventLogger = $config->getContextEventLogger(); | ||
| } | ||
|
|
||
| /** | ||
| * @param string $endpoint URL to your API endpoint. Most commonly "your-company.absmartly.io". | ||
| * @param string $apiKey API key which can be found on the Web Console. | ||
| * @param string $application Name of the application where the SDK is installed. Applications are created on the | ||
| * Web Console and should match the applications where your experiments will be running. | ||
| * @param string $environment Environment of the platform where the SDK is installed. Environments are created on | ||
| * the Web Console and should match the available environments in your infrastructure. | ||
| * @param int $retries The number of retries before the SDK stops trying to connect. | ||
| * @param int $timeout Amount of time, in milliseconds, before the SDK will stop trying to connect. | ||
| * @param callable|null $eventLogger A callback function which runs after SDK events. | ||
| * @return ABsmartly ABsmartly instance created using the credentials and details above. | ||
| */ | ||
| public static function createSimple( | ||
| string $endpoint, | ||
| string $apiKey, | ||
| string $application, | ||
| string $environment, | ||
| int $retries = 5, | ||
| int $timeout = 3000, | ||
| ?callable $eventLogger = null | ||
| ): ABsmartly { | ||
| return new ABsmartly(static::buildConfig($endpoint, $apiKey, $application, $environment, $retries, $timeout, $eventLogger)); | ||
| } | ||
|
|
||
| /** | ||
| * @deprecated Use createSimple(); this legacy signature takes environment before application. | ||
| * | ||
| * @param string $endpoint URL to your API endpoint. Most commonly "your-company.absmartly.io". | ||
| * @param string $apiKey API key which can be found on the Web Console. | ||
| * @param string $environment Environment of the platform where the SDK is installed. Environments are created on | ||
| * the Web Console and should match the available environments in your infrastructure. | ||
| * @param string $application Name of the application where the SDK is installed. Applications are created on the | ||
| * Web Console and should match the applications where your experiments will be running. | ||
| * @param int $retries The number of retries before the SDK stops trying to connect. | ||
| * @param int $timeout Amount of time, in milliseconds, before the SDK will stop trying to connect. | ||
| * @param callable|null $eventLogger A callback function which runs after SDK events. | ||
| * @return ABsmartly ABsmartly instance created using the credentials and details above. | ||
| */ | ||
| public static function createWithDefaults( | ||
| string $endpoint, | ||
| string $apiKey, | ||
| string $environment, | ||
| string $application, | ||
| int $retries = 5, | ||
| int $timeout = 3000, | ||
| ?callable $eventLogger = null | ||
| ): ABsmartly { | ||
| return new ABsmartly(static::buildConfig($endpoint, $apiKey, $application, $environment, $retries, $timeout, $eventLogger)); | ||
| } | ||
|
|
||
| protected static function buildConfig( | ||
| string $endpoint, | ||
| string $apiKey, | ||
| string $application, | ||
| string $environment, | ||
| int $retries, | ||
| int $timeout, | ||
| ?callable $eventLogger | ||
| ): Config { | ||
| $clientConfig = new ClientConfig( | ||
| $endpoint, | ||
| $apiKey, | ||
| $application, | ||
| $environment, | ||
| ); | ||
| $clientConfig->setRetries($retries); | ||
| $clientConfig->setTimeout($timeout); | ||
|
|
||
| $client = new Client($clientConfig, new HTTPClient()); | ||
| $sdkConfig = new Config($client); | ||
| if ($eventLogger !== null) { | ||
| $sdkConfig->setContextEventLogger(new \ABSmartly\SDK\Context\ContextEventLoggerCallback($eventLogger)); | ||
| } | ||
| return $sdkConfig; | ||
| } | ||
|
|
||
| public function createContext(ContextConfig $contextConfig): Context { | ||
| $this->applyEventLogger($contextConfig); | ||
| return Context::createFromContextConfig($this, $contextConfig, $this->provider, $this->handler); | ||
| } | ||
|
|
||
| public function createContextWithData(ContextConfig $contextConfig, ContextData $contextData): Context { | ||
| $this->applyEventLogger($contextConfig); | ||
| return Context::createFromContextConfig($this, $contextConfig, $this->provider, $this->handler, $contextData); | ||
| } | ||
|
|
||
| private function applyEventLogger(ContextConfig $contextConfig): void { | ||
| if ($this->eventLogger !== null && $contextConfig->getEventLogger() === null) { | ||
| $contextConfig->setEventLogger($this->eventLogger); | ||
| } | ||
| } | ||
|
|
||
| public function createContextAsync(ContextConfig $contextConfig): PromiseInterface { | ||
| $this->applyEventLogger($contextConfig); | ||
| if (!$this->provider instanceof AsyncContextDataProvider) { | ||
| try { | ||
| return resolve($this->createContext($contextConfig)); | ||
| } catch (\Throwable $e) { | ||
| return reject($e); | ||
| } | ||
| } | ||
|
|
||
| return $this->createContextPending($contextConfig)['promise']; | ||
| } | ||
|
|
||
| public function createContextPending(ContextConfig $contextConfig): array { | ||
| $this->applyEventLogger($contextConfig); | ||
| $context = Context::createPending($this, $contextConfig, $this->provider, $this->handler); | ||
|
|
||
| if (!$this->provider instanceof AsyncContextDataProvider) { | ||
| $promise = resolve(null)->then(function() use ($context) { | ||
| try { | ||
| $data = $this->provider->getContextData(); | ||
| } catch (\Throwable $error) { | ||
| $context->setContextDataFailed($error); | ||
| return $context; | ||
| } | ||
| $context->setContextData($data); | ||
| return $context; | ||
| }); | ||
| } else { | ||
| $promise = $this->provider->getContextDataAsync() | ||
| ->then( | ||
| function($data) use ($context) { | ||
| $context->setContextData($data); | ||
| return $context; | ||
| }, | ||
| function(\Throwable $error) use ($context) { | ||
| $context->setContextDataFailed($error); | ||
| return $context; | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| return ['context' => $context, 'promise' => $promise]; | ||
| } | ||
|
|
||
| public function close(): void { | ||
| $this->client->close(); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <?php | ||
|
|
||
| namespace ABSmartly\SDK\Client; | ||
|
|
||
| use ABSmartly\SDK\PublishEvent; | ||
| use React\Promise\PromiseInterface; | ||
|
|
||
| interface AsyncClientInterface extends ClientInterface { | ||
| public function getContextDataAsync(): PromiseInterface; | ||
| public function publishAsync(PublishEvent $publishEvent): PromiseInterface; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.