Repository navigation
feat: integrate PostHog feature flags with Laravel Pennant - #285
marandaneto wants to merge 19 commits into
Conversation
There was a problem hiding this comment.
PostHog Review · Standard · reviewed 0377f64: 3 Should fix.
This is the only automatic review while this pull request is a draft. Comment @posthog review to review new changes sooner. Otherwise, a review of the new changes runs when you mark it ready for review.
See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.
posthog-php-lib_curl Compliance ReportDate: 2026-10-30T08:47:21.708263+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 28ms |
| Format Validation.Event Has Uuid | ✅ | 523ms |
| Format Validation.Event Has Lib Properties | ✅ | 525ms |
| Format Validation.Distinct Id Is String | ✅ | 524ms |
| Format Validation.Token Is Present | ✅ | 524ms |
| Format Validation.Custom Properties Preserved | ✅ | 524ms |
| Format Validation.Event Has Timestamp | ✅ | 526ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 524ms |
| Retry Behavior.Retries On 503 | ✅ | 5833ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2526ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2525ms |
| Retry Behavior.Respects Retry After Header | ❌ | 6336ms |
| Retry Behavior.Implements Backoff | ✅ | 16240ms |
| Retry Behavior.Retries On 500 | ✅ | 5138ms |
| Retry Behavior.Retries On 502 | ✅ | 5632ms |
| Retry Behavior.Retries On 504 | ✅ | 5632ms |
| Retry Behavior.Max Retries Respected | ✅ | 17047ms |
| Deduplication.Generates Unique Uuids | ✅ | 231ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5631ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10841ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5634ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 531ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 526ms |
| Compression.Sends Gzip When Enabled | ✅ | 525ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 524ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 520ms |
| Batch Format.Multiple Events Batched Together | ✅ | 513ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2528ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2527ms |
| Error Handling.Retries On 408 | ✅ | 5632ms |
Failures
retry_behavior.respects_retry_after_header
Retry delay too short: 803ms < 2500ms
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 523ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 522ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 523ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 523ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 523ms |
| Request Payload.Groups Round Trip | ✅ | 522ms |
| Request Payload.Groups Default To Empty Object | ✅ | 522ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 522ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 523ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 526ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 517ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 509ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 526ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 524ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 624ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 624ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 525ms |
posthog-php-socket Compliance ReportDate: 2026-10-30T08:46:52.198026+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 29ms |
| Format Validation.Event Has Uuid | ✅ | 523ms |
| Format Validation.Event Has Lib Properties | ✅ | 524ms |
| Format Validation.Distinct Id Is String | ✅ | 525ms |
| Format Validation.Token Is Present | ✅ | 525ms |
| Format Validation.Custom Properties Preserved | ✅ | 525ms |
| Format Validation.Event Has Timestamp | ✅ | 525ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 525ms |
| Retry Behavior.Retries On 503 | ✅ | 5833ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2528ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2529ms |
| Retry Behavior.Respects Retry After Header | ❌ | 6334ms |
| Retry Behavior.Implements Backoff | ✅ | 16250ms |
| Retry Behavior.Retries On 500 | ✅ | 5633ms |
| Retry Behavior.Retries On 502 | ✅ | 5635ms |
| Retry Behavior.Retries On 504 | ✅ | 5633ms |
| Retry Behavior.Max Retries Respected | ✅ | 16248ms |
| Deduplication.Generates Unique Uuids | ✅ | 533ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5634ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10832ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5143ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 533ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 527ms |
| Compression.Sends Gzip When Enabled | ✅ | 525ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 526ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 521ms |
| Batch Format.Multiple Events Batched Together | ✅ | 512ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2528ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2528ms |
| Error Handling.Retries On 408 | ✅ | 5633ms |
Failures
retry_behavior.respects_retry_after_header
Retry delay too short: 802ms < 2500ms
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 524ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 523ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 522ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 523ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 523ms |
| Request Payload.Groups Round Trip | ✅ | 524ms |
| Request Payload.Groups Default To Empty Object | ✅ | 524ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 523ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 522ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 523ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 518ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 508ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 526ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 524ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 626ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 624ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 526ms |
posthog-php-fork_curl Compliance ReportDate: 2026-10-30T08:47:13.911553+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 39ms |
| Format Validation.Event Has Uuid | ✅ | 532ms |
| Format Validation.Event Has Lib Properties | ✅ | 536ms |
| Format Validation.Distinct Id Is String | ✅ | 537ms |
| Format Validation.Token Is Present | ✅ | 535ms |
| Format Validation.Custom Properties Preserved | ✅ | 535ms |
| Format Validation.Event Has Timestamp | ✅ | 534ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 536ms |
| Retry Behavior.Retries On 503 | ✅ | 5861ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2537ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2536ms |
| Retry Behavior.Respects Retry After Header | ❌ | 6350ms |
| Retry Behavior.Implements Backoff | ✅ | 16283ms |
| Retry Behavior.Retries On 500 | ✅ | 5650ms |
| Retry Behavior.Retries On 502 | ✅ | 5654ms |
| Retry Behavior.Retries On 504 | ✅ | 5649ms |
| Retry Behavior.Max Retries Respected | ✅ | 16286ms |
| Deduplication.Generates Unique Uuids | ✅ | 542ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5648ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10870ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5654ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 541ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 535ms |
| Compression.Sends Gzip When Enabled | ✅ | 536ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 535ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 522ms |
| Batch Format.Multiple Events Batched Together | ✅ | 519ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2534ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2535ms |
| Error Handling.Retries On 408 | ✅ | 5653ms |
Failures
retry_behavior.respects_retry_after_header
Retry delay too short: 812ms < 2500ms
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 526ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 523ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 524ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 527ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 526ms |
| Request Payload.Groups Round Trip | ✅ | 524ms |
| Request Payload.Groups Default To Empty Object | ✅ | 524ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 525ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 531ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 526ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 520ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 518ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 527ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 526ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 629ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 629ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 539ms |
* feat: add PHP framework integration primitives * fix: address framework integration review feedback * fix(flags): retain snapshot context for lazy exposure events * fix: make library metadata and release id internal integration overrides Remove library_name, library_version and release_id from the public client options. Framework integrations override internal Client methods instead, so customers keep POSTHOG_RELEASE_ID as the only documented release id input. * docs: explain when to use withContext versus enterContext * fix: address framework primitive review feedback
Bumps the github-actions group with 3 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [PostHog/posthog-sdk-test-harness/.github/workflows/test-sdk-action.yml](https://github.com/posthog/posthog-sdk-test-harness). Updates `github/codeql-action/init` from 4.38.3 to 4.39.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@24c5418...6f2ac81) Updates `github/codeql-action/analyze` from 4.38.3 to 4.39.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@24c5418...6f2ac81) Updates `PostHog/posthog-sdk-test-harness/.github/workflows/test-sdk-action.yml` from 1.15.0 to 1.19.0 - [Release notes](https://github.com/posthog/posthog-sdk-test-harness/releases) - [Changelog](https://github.com/PostHog/posthog-sdk-test-harness/blob/main/CHANGELOG.md) - [Commits](PostHog/posthog-sdk-test-harness@64ffa6a...129fbb4) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.39.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.39.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: PostHog/posthog-sdk-test-harness/.github/workflows/test-sdk-action.yml dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Manoel Aranda Neto <[email protected]>
💡 Motivation and Context
This follows the Laravel collectors PR in the stack and adds an optional, read-only PostHog store for Laravel Pennant. Pennant remains an application dependency, not a required runtime dependency of the Laravel package.
Boolean and variant reads delegate to the native SDK snapshots. Unknown results are inactive. User/model scopes use the same identifier as normal SDK capture, and Pennant's own scope contract is resolved before the driver receives it. Write operations fail explicitly instead of pretending to change remote PostHog flags.
The adapter resolves the current application rather than retaining a request manager in a singleton. Its HTTP, queue and command hooks clear Pennant's separate cache. Same-scope targeting changes within one request still require
Feature::flushCache(), as documented inPENNANT.md. Advanced flag options and payloads remain available throughevaluateFlags().The evaluate-flags and exposure contracts apply through the PHP SDK. There is no separate Pennant-specific SDK spec.
💚 How did you test it?
Decorator::get()normalizes them first. Two real public-API tests prove both custom objects and models honorFeatureScopeable; both passed without changing production code. That finding was rejected with this evidence.git diff --checkpass. Initial exact-head isolated review reports no actionable findings.📝 Checklist
If releasing new changes
.laravel-changes/.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Implemented with Pi, PHP/Composer, Git and GitHub CLI in a dedicated worktree. The adapter uses the SDK's evaluator rather than implementing flag logic. Public Pennant integration tests validate the framework contract instead of relying on assumptions from a bundle-only review. Human review is required before merge.
Reviewed commit:
0377f640e79547ded44cc36b2e38cbbb26c68685. Reviewed base:0e3243ee46def0be86f153169bc24d31876936d4. Initial isolated review has no actionable findings.Stack
Merge bottom-up:
Laravel source lives in this repository but its Composer package is released separately. Publish core ^4.20 first. The distribution repository, protected release environment and Packagist setup remain administrator prerequisites. No release has been published.
Review feedback follow-up
Current head:
bffd72ae717f224d264b73300e8a3ce4b63409a1.The driver implements scoped external flag discovery for all()/loadAll(); defined() is a non-networking local catalog, preventing accidental ambient-identity evaluation. PostHog-backed Pennant caches are flushed after actual context restoration, including sync-job failure paths and late end-event reads.
Final clean-source Laravel 12 and 13 stack (PHP 8.5 locally): 106 package tests / 664 assertions and 29 example tests / 163 assertions per version. Regression coverage includes scoped enumeration, exactly one explicit-identity request, silent listing, and four real sync-job restoration cases. Independent scoped source/vendor-aware review found no remaining actionable findings.
All three Pennant review concerns addressed. The intentional defined() semantics are documented; no mutable cross-request catalog or new store policy was added.
Direct-command context follow-up
The new upstream opt-in command trait is integrated. Pennant's independent cache now clears on console-context entry and after actual caller restoration, including direct child failures. Four new anonymous/identified parent × success/throw cases failed before and pass afterward. An isolated mutation removing only exit cleanup reproduces child-cache leakage in all four cases. Parent native snapshots are preserved without extra flag requests. Both scoped independent reviews are clean.
Core human-review integration
Signed merges integrate #282 commit
858c204: non-throwing instance group validation (static facade validation unchanged), explicit typed exception source independent of mechanism/handled, provenance protection and scope ownership docs. The core suite passes 2,532 tests / 11,366 assertions. Final committed-source Laravel 12/13 matrices remain green at 106 package tests / 664 assertions and 29 example tests / 163 assertions each. The core delta received a clean isolated review; no automatic Laravel hook-source inference was added.Before the first Laravel release: server SDK registration
posthog-laravelis not yet registered on PostHog server master for PHP usage attribution, Python/Rust feature-flag library classification/parser, or SDK health frontend/backend registries. A separate backend registration/alias PR must land before the Laravel package ships, following the approach in PostHog/posthog#93911. Event ingestion itself is not rejected just because this library identifier is unclassified. SDK health/version handling must respect Laravel's independent package version, not compare0.1.xagainst PHP core4.x. No backend PR, repository or release was created by this SDK follow-up; this remains an explicit pre-release prerequisite alongside core ^4.20 publication and distribution/Packagist setup.