diff --git a/.changeset/shiny-shoes-feel.md b/.changeset/shiny-shoes-feel.md new file mode 100644 index 0000000..34b93d9 --- /dev/null +++ b/.changeset/shiny-shoes-feel.md @@ -0,0 +1,5 @@ +--- +"posthog-php": minor +--- + +Add an optional OpenTelemetry context processor for session and person IDs on application spans. diff --git a/README.md b/README.md index 44a19b6..a28b2ff 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,16 @@ SDK usage examples and code snippets live in the official documentation so they - [Laravel integration docs](https://posthog.com/docs/libraries/laravel) - [Upgrade notes](UPGRADING.md), including GeoIP configuration and compatibility +## OpenTelemetry context + +The optional `PostHog\OpenTelemetry\ContextSpanProcessor` adds session and person IDs to application spans inside this client's request context. Register it on the application's existing OpenTelemetry provider. OpenTelemetry remains an optional application dependency. + +A client with no project key keeps request context but adds no span attributes. A valid-key client with the `noop` consumer still adds them. + +The registered processor also enables request span enrichment when `withContext()` starts. This lookup skips Fibers because uninitialized OpenTelemetry Fiber storage can return another execution path's span. Child spans still use the active Fiber's PostHog context. New Fibers and queue jobs need explicit context propagation. + +Existing span attributes take precedence. The processor preserves the application's provider, sampling, and exporters. See the [PHP tracing guide](https://posthog.com/docs/distributed-tracing/installation/php) and [Laravel guide](https://posthog.com/docs/libraries/laravel) for setup. + ## Questions? ### [Join our Slack community.](https://join.slack.com/t/posthogusers/shared_invite/enQtOTY0MzU5NjAwMDY3LTc2MWQ0OTZlNjhkODk3ZDI3NDVjMDE1YjgxY2I4ZjI4MzJhZmVmNjJkN2NmMGJmMzc2N2U3Yjc3ZjI5NGFlZDQ) diff --git a/api/public-api.json b/api/public-api.json index 63cfd03..86febe6 100644 --- a/api/public-api.json +++ b/api/public-api.json @@ -3396,6 +3396,234 @@ } } }, + "PostHog\\Internal\\SpanContext": { + "type": "class", + "abstract": false, + "final": true, + "readonly": false, + "extends": null, + "implements": [], + "constants": [], + "properties": [], + "methods": { + "enrich": { + "static": true, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "client", + "type": "PostHog\\Client", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + }, + { + "name": "span", + "type": "OpenTelemetry\\API\\Trace\\SpanInterface", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "enrichCurrent": { + "static": true, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "client", + "type": "PostHog\\Client", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "register": { + "static": true, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "client", + "type": "PostHog\\Client", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + }, + { + "name": "processor", + "type": "object", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "unregister": { + "static": true, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "client", + "type": "PostHog\\Client", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + }, + { + "name": "processor", + "type": "object", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + } + } + }, + "PostHog\\OpenTelemetry\\ContextSpanProcessor": { + "type": "class", + "abstract": false, + "final": true, + "readonly": false, + "extends": null, + "implements": [ + "OpenTelemetry\\SDK\\Trace\\SpanProcessorInterface" + ], + "constants": [], + "properties": [], + "methods": { + "__construct": { + "static": false, + "abstract": false, + "final": false, + "returnType": null, + "parameters": [ + { + "name": "client", + "type": "PostHog\\Client", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "forceFlush": { + "static": false, + "abstract": false, + "final": false, + "returnType": "bool", + "parameters": [ + { + "name": "cancellation", + "type": "?OpenTelemetry\\SDK\\Common\\Future\\CancellationInterface", + "byReference": false, + "variadic": false, + "optional": true, + "default": null, + "defaultConstant": null, + "hasDefault": true + } + ] + }, + "onEnd": { + "static": false, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "span", + "type": "OpenTelemetry\\SDK\\Trace\\ReadableSpanInterface", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "onStart": { + "static": false, + "abstract": false, + "final": false, + "returnType": "void", + "parameters": [ + { + "name": "span", + "type": "OpenTelemetry\\SDK\\Trace\\ReadWriteSpanInterface", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + }, + { + "name": "parentContext", + "type": "OpenTelemetry\\Context\\ContextInterface", + "byReference": false, + "variadic": false, + "optional": false, + "default": null, + "defaultConstant": null, + "hasDefault": false + } + ] + }, + "shutdown": { + "static": false, + "abstract": false, + "final": false, + "returnType": "bool", + "parameters": [ + { + "name": "cancellation", + "type": "?OpenTelemetry\\SDK\\Common\\Future\\CancellationInterface", + "byReference": false, + "variadic": false, + "optional": true, + "default": null, + "defaultConstant": null, + "hasDefault": true + } + ] + } + } + }, "PostHog\\PostHog": { "type": "class", "abstract": false, diff --git a/composer.json b/composer.json index aefcb41..da7fe93 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "require-dev": { "phpunit/phpunit": "^11.0", "squizlabs/php_codesniffer": "^4.0", - "ext-zlib": "*" + "ext-zlib": "*", + "open-telemetry/sdk": "^1.0" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index fcd6c27..0c803bf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "520fd5d31c8da486e608e28c4972f358", + "content-hash": "083abfeb00552b89f931e32a13c0c4ba", "packages": [ { "name": "psr/clock", @@ -383,6 +383,143 @@ } ], "packages-dev": [ + { + "name": "brick/math", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "2effe05d2177c451b86c6a073196a4034c02f211" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/2effe05d2177c451b86c6a073196a4034c02f211", + "reference": "2effe05d2177c451b86c6a073196a4034c02f211", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "require-dev": { + "phpstan/phpstan": "2.2.13", + "phpstan/phpstan-phpunit": "2.0.18", + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/1.0.0" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2026-09-12T10:28:18+00:00" + }, + { + "name": "composer/semver", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "528948210cc46f967a4b98e83b19e76f2b504993" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/528948210cc46f967a4b98e83b19e76f2b504993", + "reference": "528948210cc46f967a4b98e83b19e76f2b504993", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Version comparison library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.5.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-10-05T13:12:12+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.13.4", @@ -502,314 +639,594 @@ "time": "2025-12-06T11:56:16+00:00" }, { - "name": "phar-io/manifest", - "version": "2.0.4", + "name": "nyholm/psr7-server", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" + "url": "https://github.com/Nyholm/psr7-server.git", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/4335801d851f554ca43fa6e7d2602141538854dc", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" + "php": "^7.1 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } + "require-dev": { + "nyholm/nsa": "^1.1", + "nyholm/psr7": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" }, + "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Nyholm\\Psr7Server\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" } ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "description": "Helper classes to handle PSR-7 server requests", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" + "issues": "https://github.com/Nyholm/psr7-server/issues", + "source": "https://github.com/Nyholm/psr7-server/tree/1.1.0" }, "funding": [ { - "url": "https://github.com/theseer", + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", "type": "github" } ], - "time": "2024-03-03T12:33:53+00:00" + "time": "2023-11-08T09:30:43+00:00" }, { - "name": "phar-io/version", - "version": "3.2.1", + "name": "open-telemetry/api", + "version": "1.10.0", "source": { "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + "url": "https://github.com/opentelemetry-php/api.git", + "reference": "7c029c4a6fd457094a20569bf98f93d95e9a7559" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/7c029c4a6fd457094a20569bf98f93d95e9a7559", + "reference": "7c029c4a6fd457094a20569bf98f93d95e9a7559", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "open-telemetry/context": "^1.4", + "php": "^8.1", + "psr/log": "^1.1|^2.0|^3.0", + "symfony/polyfill-php82": "^1.26" + }, + "conflict": { + "open-telemetry/sdk": "<=1.11" }, "type": "library", + "extra": { + "spi": { + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] + }, + "branch-alias": { + "dev-main": "1.8.x-dev" + } + }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "Trace/functions.php" + ], + "psr-4": { + "OpenTelemetry\\API\\": "." + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" } ], - "description": "Library for handling version information and constraints", + "description": "API for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "api", + "apm", + "logging", + "opentelemetry", + "otel", + "tracing" + ], "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/languages/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2022-02-21T01:04:05+00:00" + "time": "2026-07-06T12:28:04+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "11.0.12", + "name": "open-telemetry/context", + "version": "1.5.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" + "url": "https://github.com/opentelemetry-php/context.git", + "reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", - "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/3c414b246e0dabb7d6145404e6a5e4536ca18d07", + "reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^5.7.0", - "php": ">=8.2", - "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-text-template": "^4.0.1", - "sebastian/code-unit-reverse-lookup": "^4.0.1", - "sebastian/complexity": "^4.0.1", - "sebastian/environment": "^7.2.1", - "sebastian/lines-of-code": "^3.0.1", - "sebastian/version": "^5.0.2", - "theseer/tokenizer": "^1.3.1" - }, - "require-dev": { - "phpunit/phpunit": "^11.5.46" + "php": "^8.1", + "symfony/polyfill-php82": "^1.26" }, "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-ffi": "To allow context switching in Fibers" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "11.0.x-dev" + "dev-main": "1.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "fiber/initialize_fiber_handler.php" + ], + "psr-4": { + "OpenTelemetry\\Context\\": "." + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "description": "Context implementation for OpenTelemetry PHP.", "keywords": [ - "coverage", - "testing", - "xunit" + "Context", + "opentelemetry", + "otel" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/languages/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", - "type": "tidelift" - } - ], - "time": "2025-12-24T07:01:01+00:00" + "time": "2025-10-19T06:44:33+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "5.1.1", + "name": "open-telemetry/sdk", + "version": "1.15.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" + "url": "https://github.com/opentelemetry-php/sdk.git", + "reference": "77e1aa73850154abb86937d52a70883edc3b4547" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", - "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/77e1aa73850154abb86937d52a70883edc3b4547", + "reference": "77e1aa73850154abb86937d52a70883edc3b4547", "shasum": "" }, "require": { - "php": ">=8.2" + "ext-json": "*", + "nyholm/psr7-server": "^1.1", + "open-telemetry/api": "^1.8", + "open-telemetry/context": "^1.4", + "open-telemetry/sem-conv": "^1.38.0", + "php": "^8.1", + "php-http/discovery": "^1.14", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.0.1|^2.0", + "psr/log": "^1.1|^2.0|^3.0", + "ramsey/uuid": "^3.0 || ^4.0", + "symfony/polyfill-mbstring": "^1.23", + "symfony/polyfill-php82": "^1.26", + "tbachert/spi": "^1.0.5" }, - "require-dev": { - "phpunit/phpunit": "^11.3" + "suggest": { + "ext-gmp": "To support unlimited number of synchronous metric readers", + "ext-mbstring": "To increase performance of string operations", + "open-telemetry/sdk-configuration": "File-based OpenTelemetry SDK configuration" }, "type": "library", "extra": { + "spi": { + "OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [ + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig", + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig", + "OpenTelemetry\\SDK\\ConfigEnv\\Trace\\SpanSuppressionStrategySemConv", + "OpenTelemetry\\SDK\\ConfigEnv\\Trace\\SpanSuppressionStrategySpanKind", + "OpenTelemetry\\SDK\\ConfigEnv\\Distribution\\DistributionConfigurationSdk" + ], + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [ + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver" + ], + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] + }, "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "1.14.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "Common/Util/functions.php", + "Logs/Exporter/_register.php", + "Metrics/MetricExporter/_register.php", + "Propagation/_register.php", + "Trace/SpanExporter/_register.php", + "Common/Dev/Compatibility/_load.php", + "_autoload.php" + ], + "psr-4": { + "OpenTelemetry\\SDK\\": "." + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "SDK for OpenTelemetry PHP.", "keywords": [ - "filesystem", - "iterator" + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "sdk", + "tracing" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/languages/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2026-07-14T13:09:54+00:00" + }, + { + "name": "open-telemetry/sem-conv", + "version": "1.44.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/sem-conv.git", + "reference": "c94bc7ebacef039cda4f4c9270807dcc58b4e3af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/c94bc7ebacef039cda4f4c9270807dcc58b4e3af", + "reference": "c94bc7ebacef039cda4f4c9270807dcc58b4e3af", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenTelemetry\\SemConv\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Semantic conventions for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "semantic conventions", + "semconv", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/languages/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2026-09-09T22:06:17+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/theseer", "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", - "type": "tidelift" + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" } ], - "time": "2026-02-02T13:52:54+00:00" + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "time": "2024-10-02T11:20:13+00:00" }, { - "name": "phpunit/php-invoker", - "version": "5.0.1", + "name": "phpunit/php-code-coverage", + "version": "11.0.12", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", "shasum": "" }, "require": { - "php": ">=8.2" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.1", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.3.1" }, "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.5.46" }, "suggest": { - "ext-pcntl": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -828,253 +1245,768 @@ "role": "lead" } ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "process" + "coverage", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2024-07-03T05:07:44+00:00" + "time": "2025-12-24T07:01:01+00:00" }, { - "name": "phpunit/php-text-template", - "version": "4.0.1", + "name": "phpunit/php-file-iterator", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T13:52:54+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.55", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.1", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.3", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-02-18T12:37:06+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-master": "3.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Log\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "template" + "log", + "psr", + "psr-3" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T05:08:43+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { - "name": "phpunit/php-timer", - "version": "7.0.1", + "name": "ramsey/collection", + "version": "2.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { - "php": ">=8.2" + "php": "^8.1" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "7.0-dev" + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Ramsey\\Collection\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "A PHP library for representing and manipulating collections.", "keywords": [ - "timer" + "array", + "collection", + "hash", + "map", + "queue", + "set" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-07-03T05:09:35+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { - "name": "phpunit/phpunit", - "version": "11.5.55", + "name": "ramsey/uuid", + "version": "4.9.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" + "url": "https://github.com/ramsey/uuid.git", + "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/75d73f48d02797c2c285a7e9f348fadc0102ffe2", + "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.12", - "phpunit/php-file-iterator": "^5.1.1", - "phpunit/php-invoker": "^5.0.1", - "phpunit/php-text-template": "^4.0.1", - "phpunit/php-timer": "^7.0.1", - "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.3", - "sebastian/diff": "^6.0.2", - "sebastian/environment": "^7.2.1", - "sebastian/exporter": "^6.3.2", - "sebastian/global-state": "^7.0.2", - "sebastian/object-enumerator": "^6.0.1", - "sebastian/recursion-context": "^6.0.3", - "sebastian/type": "^5.1.3", - "sebastian/version": "^5.0.2", - "staabm/side-effects-detector": "^1.0.5" + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { - "branch-alias": { - "dev-main": "11.5-dev" + "captainhook": { + "force-install": true } }, "autoload": { "files": [ - "src/Framework/Assert/Functions.php" + "src/functions.php" ], - "classmap": [ - "src/" - ] + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } + "MIT" ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", "keywords": [ - "phpunit", - "testing", - "xunit" + "guid", + "identifier", + "uuid" ], "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.4" }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2026-02-18T12:37:06+00:00" + "time": "2026-09-16T11:39:30+00:00" }, { "name": "sebastian/cli-parser", @@ -2198,6 +3130,138 @@ ], "time": "2024-10-20T05:08:20+00:00" }, + { + "name": "symfony/polyfill-php82", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/843bbe8b2a7934307b749751eede0f111f5d1cfc", + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-25T15:50:05+00:00" + }, + { + "name": "tbachert/spi", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/Nevay/spi.git", + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nevay/spi/zipball/e7078767866d0a9e0f91d3f9d42a832df5e39002", + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "composer/semver": "^1.0 || ^2.0 || ^3.0", + "php": "^8.1" + }, + "require-dev": { + "composer/composer": "^2.0", + "infection/infection": "^0.27.9", + "phpunit/phpunit": "^10.5", + "psalm/phar": "^5.18" + }, + "type": "composer-plugin", + "extra": { + "class": "Nevay\\SPI\\Composer\\Plugin", + "branch-alias": { + "dev-main": "1.0.x-dev" + }, + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Nevay\\SPI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Service provider loading facility", + "keywords": [ + "service provider" + ], + "support": { + "issues": "https://github.com/Nevay/spi/issues", + "source": "https://github.com/Nevay/spi/tree/v1.0.5" + }, + "time": "2025-06-29T15:42:06+00:00" + }, { "name": "theseer/tokenizer", "version": "1.3.1", diff --git a/lib/Client.php b/lib/Client.php index ebe498f..bbac1c9 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -2254,7 +2254,10 @@ private function formatDateTime(\DateTimeInterface $date): string */ public function withContext(array $data, callable $fn, array $options = []): mixed { - return RequestContext::withContext($data, $fn, $options, $this->contextKey()); + return RequestContext::withContext($data, function () use ($fn): mixed { + Internal\SpanContext::enrichCurrent($this); + return $fn(); + }, $options, $this->contextKey()); } /** diff --git a/lib/Internal/SpanContext.php b/lib/Internal/SpanContext.php new file mode 100644 index 0000000..6cc7da0 --- /dev/null +++ b/lib/Internal/SpanContext.php @@ -0,0 +1,72 @@ +isRecording()) { + return; + } + + $context = $client->getContext(); + if ($context === null || !self::isEnabled($client)) { + return; + } + + $attributes = $span->toSpanData()->getAttributes(); + foreach (['posthogDistinctId' => 'distinctId', 'sessionId' => 'sessionId'] as $attribute => $key) { + $value = $context[$key] ?? null; + if (is_string($value) && $value !== '' && !$attributes->has($attribute)) { + $span->setAttribute($attribute, $value); + } + } + } catch (\Throwable) { + } + } + + private static function isEnabled(Client $client): bool + { + return \Closure::bind(fn (): bool => $this->enabled === true, $client, Client::class)(); + } +} diff --git a/lib/OpenTelemetry/ContextSpanProcessor.php b/lib/OpenTelemetry/ContextSpanProcessor.php new file mode 100644 index 0000000..842656f --- /dev/null +++ b/lib/OpenTelemetry/ContextSpanProcessor.php @@ -0,0 +1,48 @@ +shutdown) { + SpanContext::enrich($this->client, $span); + } + } + + public function onEnd(ReadableSpanInterface $span): void + { + } + + public function forceFlush(?CancellationInterface $cancellation = null): bool + { + return !$this->shutdown; + } + + public function shutdown(?CancellationInterface $cancellation = null): bool + { + if ($this->shutdown) { + return false; + } + + $this->shutdown = true; + SpanContext::unregister($this->client, $this); + return true; + } +} diff --git a/test/ContextSpanProcessorTest.php b/test/ContextSpanProcessorTest.php new file mode 100644 index 0000000..cee4a90 --- /dev/null +++ b/test/ContextSpanProcessorTest.php @@ -0,0 +1,265 @@ +client = new Client('test-token', [], new MockedHttpClient('example.com'), 'test'); + } + + public function testExistingRequestSpanAndChildKeepTheirContextAfterScopeEnds(): void + { + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($this->client))->build(); + $tracer = $provider->getTracer('test'); + $request = $tracer->spanBuilder('request')->startSpan(); + $scope = $request->activate(); + try { + $child = $this->client->withContext( + $this->client->contextFromHeaders([ + 'X-PostHog-Distinct-Id' => 'browser-user', + 'X-PostHog-Session-Id' => 'browser-session', + ]), + fn () => $tracer->spanBuilder('child')->startSpan(), + ['fresh' => true] + ); + } finally { + $scope->detach(); + } + $late = $tracer->spanBuilder('late')->startSpan(); + foreach ([$request, $child] as $span) { + $span->end(); + $this->assertSame([ + 'posthogDistinctId' => 'browser-user', + 'sessionId' => 'browser-session', + ], $span->toSpanData()->getAttributes()->toArray()); + } + $this->assertSame([], $late->toSpanData()->getAttributes()->toArray()); + $late->end(); + } + + public static function disabledClientKeys(): array + { + return ['missing' => [null, false], 'empty' => ['', false], 'blank' => [" \t ", false], + 'enabled noop consumer' => ['test-token', true]]; + } + + #[DataProvider('disabledClientKeys')] + public function testClientStateControlsContextAndSpanEnrichment(?string $apiKey, bool $enabled): void + { + $client = new Client($apiKey, ['consumer' => 'noop']); + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($client))->build(); + $tracer = $provider->getTracer('test'); + $request = $tracer->spanBuilder('request')->setAttribute('app.key', 'keep')->startSpan(); + $scope = $request->activate(); + try { + $child = $client->withContext( + ['distinctId' => 'user', 'sessionId' => 'session'], + function () use ($client, $tracer) { + $this->assertSame('user', $client->getContext()['distinctId'] ?? null); + return $tracer->spanBuilder('child')->startSpan(); + } + ); + } finally { + $scope->detach(); + $request->end(); + } + $child->end(); + $ids = $enabled ? ['posthogDistinctId' => 'user', 'sessionId' => 'session'] : []; + $this->assertSame(['app.key' => 'keep'] + $ids, $request->toSpanData()->getAttributes()->toArray()); + $this->assertSame($ids, $child->toSpanData()->getAttributes()->toArray()); + $this->assertNull($client->getContext()); + } + + public static function identityCases(): array + { + return [ + 'explicit identities' => [ + ['posthogDistinctId' => 'app-user', 'sessionId' => 'app-session'], + ['distinctId' => 'header-user', 'sessionId' => 'header-session'], + ['posthogDistinctId' => 'app-user', 'sessionId' => 'app-session'], + ], + 'explicit user' => [ + ['posthogDistinctId' => 'app-user'], + ['distinctId' => 'header-user', 'sessionId' => 'header-session'], + ['posthogDistinctId' => 'app-user', 'sessionId' => 'header-session'], + ], + 'empty explicit values' => [ + ['posthogDistinctId' => '', 'sessionId' => ''], + ['distinctId' => 'header-user', 'sessionId' => 'header-session'], + ['posthogDistinctId' => '', 'sessionId' => ''], + ], + 'missing IDs' => [[], [], []], + 'user only' => [[], ['distinctId' => 'user'], ['posthogDistinctId' => 'user']], + 'session only' => [[], ['sessionId' => 'session'], ['sessionId' => 'session']], + ]; + } + + #[DataProvider('identityCases')] + public function testPreservesExplicitAttributesAndOmitsMissingIds( + array $attributes, + array $context, + array $expected + ): void { + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($this->client))->build(); + $span = $this->client->withContext( + $context, + fn () => $provider->getTracer('test')->spanBuilder('child')->setAttributes($attributes)->startSpan() + ); + $span->end(); + $this->assertSame($expected, $span->toSpanData()->getAttributes()->toArray()); + } + + public function testRegisteredBridgePreservesCapacity(): void + { + $provider = new TracerProvider( + [new ContextSpanProcessor($this->client)], + spanLimits: (new SpanLimitsBuilder())->setAttributeCountLimit(1)->build() + ); + $span = $provider->getTracer('test')->spanBuilder('request')->setAttribute('app.key', 'keep')->startSpan(); + $scope = $span->activate(); + try { + $this->client->withContext(['distinctId' => 'user', 'sessionId' => 'session'], static fn () => null); + } finally { + $scope->detach(); + $span->end(); + } + $this->assertSame(['app.key' => 'keep'], $span->toSpanData()->getAttributes()->toArray()); + } + + public function testConcurrentFibersAndFreshScopesStayIsolated(): void + { + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($this->client))->build(); + $tracer = $provider->getTracer('test'); + $makeFiber = fn (string $id) => new \Fiber(function () use ($id, $tracer) { + $scope = Context::getRoot()->activate(); + try { + return $this->client->withContext( + ['distinctId' => $id, 'sessionId' => $id], + static function () use ($tracer) { + \Fiber::suspend(); + return $tracer->spanBuilder('child')->startSpan(); + }, + ['fresh' => true] + ); + } finally { + $scope->detach(); + } + }); + $first = $makeFiber('first'); + $second = $makeFiber('second'); + $first->start(); + $second->start(); + foreach (['second' => $second, 'first' => $first] as $id => $fiber) { + $fiber->resume(); + $span = $fiber->getReturn(); + $span->end(); + $this->assertSame($id, $span->toSpanData()->getAttributes()->get('sessionId')); + } + $span = $this->client->withContext( + ['sessionId' => 'outer'], + fn () => $this->client->withContext( + ['distinctId' => 'fresh-user'], + fn () => $tracer->spanBuilder('fresh')->startSpan(), + ['fresh' => true] + ) + ); + $span->end(); + $this->assertSame(['posthogDistinctId' => 'fresh-user'], $span->toSpanData()->getAttributes()->toArray()); + $this->assertNull($this->client->getContext()); + } + + public function testClientIsolationAndFailureCleanup(): void + { + $other = new Client('other-token', [], new MockedHttpClient('example.com'), 'test'); + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($this->client))->build(); + $tracer = $provider->getTracer('test'); + try { + $other->withContext(['sessionId' => 'other-session'], function () use ($tracer): void { + $span = $tracer->spanBuilder('other-client')->startSpan(); + $span->end(); + $this->assertSame([], $span->toSpanData()->getAttributes()->toArray()); + }); + $this->client->withContext(['sessionId' => 'failed-session'], static function (): void { + throw new \RuntimeException('test failure'); + }); + $this->fail('Expected the application exception'); + } catch (\RuntimeException $exception) { + $this->assertSame('test failure', $exception->getMessage()); + } + $span = $tracer->spanBuilder('after-failure')->startSpan(); + $span->end(); + $this->assertSame([], $span->toSpanData()->getAttributes()->toArray()); + } + + public function testUninitializedFiberCannotChangeMainSpan(): void + { + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($this->client))->build(); + $main = $provider->getTracer('test')->spanBuilder('main')->startSpan(); + $scope = $main->activate(); + try { + $fiber = new \Fiber(fn () => $this->client->withContext( + ['distinctId' => 'fiber-user', 'sessionId' => 'fiber-session'], + static fn () => null + )); + $fiber->start(); + $this->assertTrue($fiber->isTerminated()); + } finally { + $scope->detach(); + $main->end(); + } + $this->assertSame([], $main->toSpanData()->getAttributes()->toArray()); + } + + public function testDefaultContextDoesNotChangeActiveSpan(): void + { + $provider = TracerProvider::builder()->build(); + $span = $provider->getTracer('test')->spanBuilder('request')->startSpan(); + $scope = $span->activate(); + try { + $this->client->withContext(['sessionId' => 'unregistered'], static fn () => null); + } finally { + $scope->detach(); + $span->end(); + } + $this->assertSame([], $span->toSpanData()->getAttributes()->toArray()); + } + + public function testContextLookupFailureDoesNotBreakApplicationTracing(): void + { + $client = $this->createMock(Client::class); + $client->method('getContext')->willThrowException(new \RuntimeException('lookup failure')); + $provider = TracerProvider::builder()->addSpanProcessor(new ContextSpanProcessor($client))->build(); + $span = $provider->getTracer('test')->spanBuilder('application')->startSpan(); + $this->assertTrue($span->isRecording()); + $span->end(); + $this->assertSame([], $span->toSpanData()->getAttributes()->toArray()); + } + + public function testProcessorShutdownStopsNewChildEnrichment(): void + { + $processor = new ContextSpanProcessor($this->client); + $provider = TracerProvider::builder()->addSpanProcessor($processor)->build(); + $this->assertTrue($processor->forceFlush()); + $this->assertTrue($processor->shutdown()); + $this->assertFalse($processor->shutdown()); + $span = $this->client->withContext( + ['sessionId' => 'session'], + fn () => $provider->getTracer('test')->spanBuilder('after-shutdown')->startSpan() + ); + $span->end(); + $this->assertSame([], $span->toSpanData()->getAttributes()->toArray()); + } +}