Skip to content

feat: add the Laravel runtime package - #283

Merged
marandaneto merged 27 commits into
mainfrom
feat/laravel-package-runtime
Nov 2, 2026
Merged

marandaneto merged 27 commits into
mainfrom
feat/laravel-package-runtime

Conversation

@marandaneto

@marandaneto marandaneto commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

💡 Motivation and Context

Add the independently versioned posthog/posthog-laravel package under packages/laravel. It depends on the already-published PHP SDK ^4.20. Core framework support landed in #282 and was released in 4.20.0. This branch now includes current main, so the PR no longer repeats those core implementation changes.

Composer discovery registers scoped services, a facade and an injectable core client. The package adds lazy SDK construction, authenticated/header analytics context, safe route metadata, bounded queue propagation, Laravel exception reporting, request-scoped flag snapshots, an optional Laravel Cache definition provider, diagnostics and a test fake.

Public API boundary

The supported surface is the manager/facade, documented Context APIs, the IsolatesCommandContext trait, test fake, service provider registration and the Laravel cache adapter. Framework-only classes are marked @internal and are final where appropriate. Public framework callback methods are not supported customer extension points.

The manager declares supported client conveniences explicitly instead of forwarding arbitrary methods through __call(). These include the five legacy flag getters, loadFlags() and contextFromHeaders(). Legacy getters inherit current context groups when no nonempty groups argument is supplied, consistently with evaluateFlags(). Explicit nonempty groups override the ambient groups. Other lower-level operations remain available through client(). The new api/laravel-public-api.json snapshot tracks the Laravel API separately from core and excludes internal classes and members. CI checks both snapshots and runs the Laravel tests.

Lifecycles and exceptions

HTTP context remains active through kernel termination and after-response callbacks. Queue and sequential Octane boundaries flush and clean context. Console integration decorates the application's existing kernel and restores nested kernel calls in finally.

Direct child-command call(), callSilent() and callSilently() isolation is opt-in: apply PostHog\Laravel\Concerns\IsolatesCommandContext to the calling command or the application's base command. Uninstrumented callers, including closure commands, still need kernel-level Artisan::call() or an explicit context scope. There is no global command interception, vendor patching or reflection-based wrapping.

Automatic exception reporting reuses captureException() inside a fresh saved-context scope. It preserves the original identity, groups and boundary metadata without a second reportException() API. Automatic reports of the same exception object are deduplicated. Laravel retains ownership of PHP handlers and exception-reporting policy.

Known hooks supply stable $exception_source values: laravel.middleware, laravel.queue_job, laravel.console_lifecycle and laravel.octane_worker_error. The registered reportable callback supplies laravel.reportable when no earlier source was remembered. Sources are not inferred from stack frames. Ordinary manual captures and direct reporter calls with no known hook omit the source. Later boundaries do not overwrite an earlier known source.

Safety and compatibility

Missing credentials and explicit disablement prevent SDK network paths. Defaults exclude raw URLs, query strings, bodies, cookies, email, visitor IP and implicit profile synchronization. GeoIP defaults off. PostHog tracing headers and queue metadata are untrusted analytics context, never authentication or authorization evidence.

Custom HTTP kernels without Laravel's lifecycle callback API cannot provide automatic after-response correlation. Overlapping shared-container Swoole coroutines are not supported. Do not retain scoped services in application singletons or prewarm them in Octane.

Capture, identify, alias, groups, exception metadata, feature-flag evaluation and tracing-header contracts reuse the PHP SDK. The core's pre-existing wire behavior outside the integration changes is not changed by this PR.

Core distribution boundary

The root .gitattributes excludes packages/laravel/ from core Composer archives. Laravel source remains available in Git and is not registered by the core autoloader. An archive regression test confirms the exclusion while retaining the core manifest, runtime and root license. The test failed before the attribute was added and passes afterward.

Source-copy tools must use a full Git checkout rather than a whole-repository git archive, which now excludes Laravel. The helper in the later tooling PR #287 needs that adjustment before it lands. Core release versioning remains scoped to the root package.

💚 How did you test it?

  • PHP 8.5.10: core suite passes 2,534 tests / 11,379 assertions.
  • Laravel runtime suite passes 67 tests / 452 assertions on both Laravel 12 and 13. Clean dependency installs use the published PHP SDK 4.20.0.
  • Before/after regressions verify that automatic reports use captureException(), preserve context through failures, and attach known source metadata without leaking it into manual captures.
  • Before/after API tests verify that internal and future core methods are not exposed automatically. Supported flag methods preserve named arguments and restore core context. Five inherited-group cases and a real-core HTTP payload regression failed before the ambient-group fix and pass afterward.
  • Snapshot filtering tests verify that internal classes and members are excluded only for the Laravel snapshot. A deliberate stale-snapshot mutation is rejected by the checker. The core snapshot is unchanged.
  • Existing tests cover real HTTP termination, late callbacks, nested Artisan commands, queue failures, user-context isolation, Octane sandbox events, local definitions, disabled clients and fakes.
  • Scoped PHP_CodeSniffer, actionlint, Composer validation, both API snapshot checks and git diff --check pass.
  • The runtime/API closeout at d172504cf848badbb6d34936c61a2b0c0fd80066 received a clean isolated review after its ambient-group finding was reproduced and fixed in a separate signed commit. The archive closeout at 4837e6877a5847e1e3ae6ffae6642df282492c0b also received a clean isolated review.

📝 Checklist

  • All commits are signed.
  • Reviewed the submitted code and retained regression tests.
  • Updated documentation for the public API and exception provenance.
  • Added separate Laravel API snapshot checks and test execution to CI.
  • Removed the already-consumed core release intent.
  • Removed the Laravel initial-release intent for now. This PR does not publish a Laravel release.

🤖 Agent context

Autonomy: Human-driven (agent-assisted).

Implemented and validated with Pi, PHP/Composer, Git and GitHub CLI in the existing dedicated runtime worktree. The user requested the API naming changes, reporting consolidation, internal API restrictions, explicit forwarding, API snapshot coverage, shared-license symlink and removal of release intents. Human review is still required before merge.

Stack

Merge bottom-up:

  1. feat: add PHP framework integration primitives #282: PHP framework support. Merged and released in PHP 4.20.0.
  2. feat: add the Laravel runtime package #283: Laravel runtime.
  3. feat: add opt-in Laravel autocapture #284: Optional server autocapture.
  4. feat: integrate PostHog feature flags with Laravel Pennant #285: Read-only Pennant integration.
  5. feat: add a runnable Laravel SDK example #286: Runnable Laravel example.
  6. ci: release the Laravel package independently #287: Independent CI and publishing.

Laravel source lives in this repository but its Composer package is released separately. packages/laravel/LICENSE.md is a relative symlink to the repository's shared license. The later distribution tooling must materialize that file when exporting a standalone package.

Before the first Laravel release

posthog-laravel still needs server registration/aliases for PHP usage attribution, Python/Rust feature-flag library classification/parsing, and SDK health registries, following PostHog/posthog#93911. Its independent 0.1.x package version must not be compared against PHP core 4.x. Event ingestion is not rejected solely because the library identifier is unclassified.

The distribution repository, protected release environment and Packagist setup remain administrator prerequisites. Release intents and the standalone publisher belong with the release tooling, not this runtime PR. No Laravel release has been published.

@marandaneto marandaneto self-assigned this Oct 29, 2026
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-fork_curl Compliance Report

Date: 2026-11-02T10:36:28.061723+00:00
Duration: 116126ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 39ms
Format Validation.Event Has Uuid ✅ 532ms
Format Validation.Event Has Lib Properties ✅ 534ms
Format Validation.Distinct Id Is String ✅ 532ms
Format Validation.Token Is Present ✅ 534ms
Format Validation.Custom Properties Preserved ✅ 535ms
Format Validation.Event Has Timestamp ✅ 533ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 533ms
Retry Behavior.Retries On 503 ✅ 5859ms
Retry Behavior.Does Not Retry On 400 ✅ 2535ms
Retry Behavior.Does Not Retry On 401 ✅ 2535ms
Retry Behavior.Respects Retry After Header ❌ 6352ms
Retry Behavior.Implements Backoff ✅ 16282ms
Retry Behavior.Retries On 500 ✅ 5653ms
Retry Behavior.Retries On 502 ✅ 5648ms
Retry Behavior.Retries On 504 ✅ 5648ms
Retry Behavior.Max Retries Respected ✅ 16284ms
Deduplication.Generates Unique Uuids ✅ 541ms
Deduplication.Preserves Uuid On Retry ✅ 5652ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10860ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5162ms
Deduplication.No Duplicate Events In Batch ✅ 540ms
Deduplication.Different Events Have Different Uuids ✅ 537ms
Compression.Sends Gzip When Enabled ✅ 536ms
Batch Format.Uses Proper Batch Structure ✅ 533ms
Batch Format.Flush With No Events Sends Nothing ✅ 521ms
Batch Format.Multiple Events Batched Together ✅ 526ms
Error Handling.Does Not Retry On 403 ✅ 2536ms
Error Handling.Does Not Retry On 413 ✅ 2537ms
Error Handling.Retries On 408 ✅ 5648ms

Failures

retry_behavior.respects_retry_after_header

Retry delay too short: 811ms < 2500ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 525ms
Request Payload.Flags Request Uses V2 Query Param ✅ 523ms
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 ✅ 523ms
Request Payload.Groups Default To Empty Object ✅ 522ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 524ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 525ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 524ms
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 ✅ 627ms
Retry Behavior.Retries Flags On 504 ✅ 625ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 535ms

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostHog Review · Standard · reviewed 3b2ad5a: 7 Should fix. This pull request is large, so the review ran in 4 parts with less depth than usual.

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.

Comment thread packages/laravel/src/ConsoleKernel.php Resolved
Comment thread packages/laravel/src/PostHog.php Resolved
Comment thread packages/laravel/src/Context.php Resolved
Comment thread packages/laravel/src/PostHog.php Resolved
Comment thread packages/laravel/src/PostHog.php Resolved
Comment thread packages/laravel/src/PostHog.php Resolved
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-socket Compliance Report

Date: 2026-11-02T10:36:20.087332+00:00
Duration: 115093ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 26ms
Format Validation.Event Has Uuid ✅ 522ms
Format Validation.Event Has Lib Properties ✅ 524ms
Format Validation.Distinct Id Is String ✅ 522ms
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 ✅ 523ms
Retry Behavior.Retries On 503 ✅ 5832ms
Retry Behavior.Does Not Retry On 400 ✅ 2524ms
Retry Behavior.Does Not Retry On 401 ✅ 2531ms
Retry Behavior.Respects Retry After Header ❌ 6330ms
Retry Behavior.Implements Backoff ✅ 16248ms
Retry Behavior.Retries On 500 ✅ 5635ms
Retry Behavior.Retries On 502 ✅ 5629ms
Retry Behavior.Retries On 504 ✅ 5636ms
Retry Behavior.Max Retries Respected ✅ 16239ms
Deduplication.Generates Unique Uuids ✅ 36ms
Deduplication.Preserves Uuid On Retry ✅ 5632ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10833ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5139ms
Deduplication.No Duplicate Events In Batch ✅ 528ms
Deduplication.Different Events Have Different Uuids ✅ 528ms
Compression.Sends Gzip When Enabled ✅ 524ms
Batch Format.Uses Proper Batch Structure ✅ 525ms
Batch Format.Flush With No Events Sends Nothing ✅ 520ms
Batch Format.Multiple Events Batched Together ✅ 511ms
Error Handling.Does Not Retry On 403 ✅ 2526ms
Error Handling.Does Not Retry On 413 ✅ 2526ms
Error Handling.Retries On 408 ✅ 5632ms

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 ✅ 523ms
Request Payload.Flags Request Uses V2 Query Param ✅ 521ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 521ms
Request Payload.Flags Request Omits Authorization Header ✅ 522ms
Request Payload.Token In Flags Body Matches Init ✅ 521ms
Request Payload.Groups Round Trip ✅ 520ms
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 ✅ 519ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 521ms
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 ✅ 523ms
Retry Behavior.Retries Flags On 502 ✅ 625ms
Retry Behavior.Retries Flags On 504 ✅ 624ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 524ms

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-lib_curl Compliance Report

Date: 2026-11-02T10:36:10.001037+00:00
Duration: 114897ms

⚠️ Some Tests Failed

46/47 tests passed, 1 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 24ms
Format Validation.Event Has Uuid ✅ 520ms
Format Validation.Event Has Lib Properties ✅ 520ms
Format Validation.Distinct Id Is String ✅ 522ms
Format Validation.Token Is Present ✅ 521ms
Format Validation.Custom Properties Preserved ✅ 521ms
Format Validation.Event Has Timestamp ✅ 521ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 521ms
Retry Behavior.Retries On 503 ✅ 5828ms
Retry Behavior.Does Not Retry On 400 ✅ 2525ms
Retry Behavior.Does Not Retry On 401 ✅ 2522ms
Retry Behavior.Respects Retry After Header ❌ 6326ms
Retry Behavior.Implements Backoff ✅ 15739ms
Retry Behavior.Retries On 500 ✅ 5136ms
Retry Behavior.Retries On 502 ✅ 5628ms
Retry Behavior.Retries On 504 ✅ 5629ms
Retry Behavior.Max Retries Respected ✅ 17044ms
Deduplication.Generates Unique Uuids ✅ 227ms
Deduplication.Preserves Uuid On Retry ✅ 5628ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10829ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5137ms
Deduplication.No Duplicate Events In Batch ✅ 525ms
Deduplication.Different Events Have Different Uuids ✅ 522ms
Compression.Sends Gzip When Enabled ✅ 521ms
Batch Format.Uses Proper Batch Structure ✅ 520ms
Batch Format.Flush With No Events Sends Nothing ✅ 518ms
Batch Format.Multiple Events Batched Together ✅ 510ms
Error Handling.Does Not Retry On 403 ✅ 2523ms
Error Handling.Does Not Retry On 413 ✅ 2523ms
Error Handling.Retries On 408 ✅ 5628ms

Failures

retry_behavior.respects_retry_after_header

Retry delay too short: 804ms < 2500ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 520ms
Request Payload.Flags Request Uses V2 Query Param ✅ 520ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 519ms
Request Payload.Flags Request Omits Authorization Header ✅ 519ms
Request Payload.Token In Flags Body Matches Init ✅ 519ms
Request Payload.Groups Round Trip ✅ 519ms
Request Payload.Groups Default To Empty Object ✅ 519ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 519ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 520ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 519ms
Request Lifecycle.No Flags Request On Init Alone ✅ 516ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 507ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 523ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 520ms
Retry Behavior.Retries Flags On 502 ✅ 622ms
Retry Behavior.Retries Flags On 504 ✅ 621ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 522ms

Base automatically changed from feat/php-framework-integration to main October 29, 2026 17:05
@marandaneto
marandaneto marked this pull request as ready for review October 30, 2026 12:13
@marandaneto
marandaneto requested a review from a team as a code owner October 30, 2026 12:13

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostHog Review · Standard · reviewed 4837e68: 2 Should fix. This pull request is large, so the review ran in 4 parts with less depth than usual.

Comment thread packages/laravel/src/Context.php Resolved

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostHog Review · Standard · reviewed ca087b8: no findings. This pull request is large, so the review ran in 4 parts with less depth than usual.

@ioannisj ioannisj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 An AI agent wrote this on behalf of @ioannisj.

Nothing blocking from my side. The two I'd look at before the first Laravel release are the per-scope definitions refetch when flag_cache is off and the unguarded lazy client build in packages/laravel/src/PostHog.php, the rest are smaller and could be follow-ups.

Comment thread packages/laravel/src/PostHog.php Resolved
Comment thread packages/laravel/src/PostHog.php Resolved
Comment thread packages/laravel/src/Context.php Resolved
Comment thread packages/laravel/src/PostHog.php Resolved
Comment thread packages/laravel/src/Context.php Resolved
@marandaneto

Copy link
Copy Markdown
Member Author

Addressed the eight follow-up threads in signed commit 33a71f9, with focused before/after regressions and replies on each thread.

Validation: 78 tests / 524 assertions on both Laravel 12.70.2 and 13.39.0 using the published PHP SDK 4.20.0; 50 focused native retry tests / 554 assertions; PHP_CodeSniffer, both API snapshots, and git diff --check pass. Existing worktree dependencies were preserved; temporary matrix installs were removed.

The compliance Retry-After failure was investigated separately, not claimed as fixed: the main-branch report already has exactly the same “Retry delay too short: 804ms < 2500ms” failure as this PR. The adapter documents a capped ~800ms retry delay for this profile, while the harness expects at least 2500ms. Core transport and adapter files are unchanged by this PR; no speculative retry-policy change was included.

@marandaneto
marandaneto merged commit 0a87806 into main Nov 2, 2026
27 checks passed
@marandaneto
marandaneto deleted the feat/laravel-package-runtime branch November 2, 2026 10:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants