Conversation
Implements the Hooks specification (evaluation series + track series) to give users and LaunchDarkly integration packages an extension point for observing flag evaluations and track calls. - Public API under `LaunchDarkly\Hooks`: `Hook` (abstract base class), `Metadata`, `EvaluationSeriesContext`, `TrackSeriesContext`. - `LDClient` accepts a `hooks` option at construction and exposes `addHook()` for runtime registration. - `beforeEvaluation` / `afterEvaluation` fire around every `variation`, `variationDetail`, and `migrationVariation` call, with `afterEvaluation` running in reverse registration order per spec 1.3.4. - `afterTrack` fires after a custom event is enqueued. It does not fire for `trackMigrationOperation` (not a custom event) or for `track` calls rejected due to an invalid context. - Hook exceptions are caught and logged at the error level; when a stage errors, subsequent stages receive the previous successful stage's data (spec 1.3.7.1). - `environmentId` is intentionally omitted from the series contexts until the SDK can populate it reliably; the spec lists it as optional. - Identify series (1.4) and configuration handlers (1.5) are skipped because they are client-side only. - Contract test service declares `evaluation-hooks` and `track-hooks` capabilities and wires harness-provided hook configs through a new `PostingHook` implementation.
CI flagged an UnusedBaselineEntry for NullEventProcessor's UnusedClass error. The entry was added when I regenerated the baseline locally, but CI's psalm doesn't produce that error — so the baseline entry is stale from CI's perspective. Remove it to match what CI sees. The root cause of the local/CI divergence appears to be environmental (same psalm 6.16.1, --no-cache, same composer.lock) but isn't worth chasing for this PR.
Co-authored-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8f7b5e0. Configure here.
| $runner->afterEvaluation($this->ctx(), $before, $this->detail()); | ||
|
|
||
| // Hook A's afterEvaluation receives [] because its beforeEvaluation failed. | ||
| $this->assertSame([], $a->calls[0]['data']); |
There was a problem hiding this comment.
Test asserts wrong index, trivially passes always
Medium Severity
The assertion $a->calls[0]['data'] checks the beforeEvaluation record (index 0), whose data field is always [] because HookRunner::beforeEvaluation unconditionally passes an empty array to every hook. The comment says "Hook A's afterEvaluation receives [] because its beforeEvaluation failed," so the assertion needs to use $a->calls[1]['data'] (the afterEvaluation record) to actually verify the error recovery behavior described by spec 1.3.7.1. As written, this test passes regardless of whether the fallback-on-error logic works correctly.
Reviewed by Cursor Bugbot for commit 8f7b5e0. Configure here.
RecordingHook took its shared log as a by-reference constructor-promoted parameter (`private ?array &$sharedLog`). PHP CS Fixer 3.80.0 — the version CI ends up running under the prefer-lowest matrix entry — has a bug in its visibility_required fixer that mutates this into invalid syntax (`private ?array &public $sharedLog`), breaking `make lint`. Swap the reference for a small CallLog value object that holds a mutable calls array. Cross-hook ordering tests read `$shared->calls` instead of relying on PHP array-by-reference semantics.


Implements the Hooks specification (evaluation series + track series) to
give users and LaunchDarkly integration packages an extension point for
observing flag evaluations and track calls.
LaunchDarkly\Hooks:Hook(abstract base class),Metadata,EvaluationSeriesContext,TrackSeriesContext.LDClientaccepts ahooksoption at construction and exposesaddHook()for runtime registration.beforeEvaluation/afterEvaluationfire around everyvariation,variationDetail, andmigrationVariationcall, withafterEvaluationrunning in reverse registration order per spec 1.3.4.
afterTrackfires after a custom event is enqueued. It does not firefor
trackMigrationOperation(not a custom event) or fortrackcallsrejected due to an invalid context.
errors, subsequent stages receive the previous successful stage's data
(spec 1.3.7.1).
environmentIdis intentionally omitted from the series contexts untilthe SDK can populate it reliably; the spec lists it as optional.
because they are client-side only.
evaluation-hooksandtrack-hookscapabilities and wires harness-provided hook configs through a new
PostingHookimplementation.Note
Medium Risk
Touches core
LDClientevaluation/track paths by adding hook dispatch and refactoring evaluation intoevaluateInternal, which could affect ordering/performance and error handling if hooks misbehave. Failures are isolated and logged, but this introduces new execution around every flag evaluation and custom event.Overview
Adds a new public hooks API (
LaunchDarkly\Hooks\Hook,Metadata,EvaluationSeriesContext,TrackSeriesContext) and an internalHookRunnerto run hook stages with defined ordering and exception isolation.LDClientnow accepts ahooksconstructor option and exposesaddHook(). It wrapsvariation,variationDetail, andmigrationVariationwithbeforeEvaluation/afterEvaluation(after runs in reverse registration order) and invokesafterTrackafter successful customtrack()enqueues, while intentionally not firing hooks for invalid-track calls ortrackMigrationOperation.Updates the contract test service to advertise hook capabilities and to construct a
PostingHookfrom harness config; adds unit tests covering ordering, data propagation, and logging behavior. Updatespsalm-baseline.xmlaccordingly.Reviewed by Cursor Bugbot for commit b68661a. Bugbot is set up for automated code reviews on this repo. Configure here.