diff --git a/content/en/docs/languages/php/context.md b/content/en/docs/languages/php/context.md new file mode 100644 index 000000000000..9684253d22db --- /dev/null +++ b/content/en/docs/languages/php/context.md @@ -0,0 +1,178 @@ +--- +title: Context +weight: 55 +cSpell:ignore: Swoole +description: Learn how the context API works in instrumented applications. +--- + +OpenTelemetry works by storing and propagating telemetry data. For example, when +an instrumented application receives a request and a span starts, the span must +be available to a component which creates child spans. To address this need, +OpenTelemetry stores the span in the active context. + +## PHP execution context + +The context API is globally available within a single PHP execution context, and +there can only be one [active context](#active-context) in the current execution +context. + +### Storage + +Context can store values (for example, a `Span`), and it uses `Storage` to keep +track of the stored values. By default, a generic `ContextStorage` is used. +OpenTelemetry for PHP supports other context storage for less common use cases, +like asynchronous or concurrent execution with `fibers`. + +## Context keys + +Values as stored in context as key-value pairs. Context keys are used to store +and retrieve values from context. + +Keys can be created by calling `OpenTelemetry\Context\Context::createKey()`, for +example: + +```php +use OpenTelemetry\Context\Context; + +$key1 = Context::createKey('My first key'); +$key2 = Context::createKey('My second key'); +``` + +## Active context + +The active context is the context which is returned by `Context::getCurrent()`. +The context object contains entries which allow telemetry components to +communicate with each other. For example, when a span is created it can be +activated, which creates a new active context and stores the span. Later, when +another span is created it can use the span from the active context as its +parent span. If no context is active, the root context is returned, which is +just the empty context object. + +```php +use OpenTelemetry\Context\Context; + +// Returns the active context +// If no context is active, the root context is returned +$context = Context::getCurrent(); +``` + +### Set and get context values + +Values are stored in Context by using the `$context->with($key, $value)` method. +Setting a context entry creates a new context with the new entry in its storage, +containing `$value`. + +Context is immutable. Setting a context entry creates a new context with the new +entry in its storage: `$context->with($key, $value)`. Retrieve values using +`$context->get($key)`, for example: + +```php +use OpenTelemetry\Context\Context; + +$key = Context::createKey('some key'); + +// add a new entry +$ctx2 = Context::getCurrent()->with($key, 'context 2'); + +// ctx2 contains the new entry +var_dump($ctx2->get($key)); // "context 2" + +// active context is unchanged +var_dump(Context::getCurrent()->get($key)); // NULL +``` + +If a value is not found in the current context, then each parent is checked +until either the key is found, or the root context is reached. + +### Activate a context + +A context can be made active by calling `$context->activate()`. + +```php +use OpenTelemetry\Context\Context; + +$key = Context::createKey('my-key'); +$ctx = Context::getCurrent(); +$ctx2 = $ctx->with($key, 'context 2'); +$ctx2->activate(); +assert($ctx2 === Context::getCurrent()); +``` + +#### Scope + +The return value of `$context->activate()` is a `Scope`. You must `detach()` the +scope to deactivate that context, which reactivates the previously-active +context. + +The return value of `$scope->detach()` is an integer. A return value of `0` +means that the scope was successfully detached. A non-zero value means that the +call was unexpected. This could happen if the context associated with the scope +was: + +- Already detached +- Not a part of the current execution context +- Not the active context + +#### DebugScope + +To assist developers in locating issues with context and scope, there is +`DebugScope`. In a PHP runtime with assertions enabled, an activated `Context` +is wrapped in a `DebugScope`. The `DebugScope` keeps track of when the scope was +activated, and has a destructor which triggers an error if the scope was not +detached. The error output contains a backtrace of which code activated the +context. + +The following code would trigger an error, complaining that a scope was not +detached, and giving a backtrace of where the scope was created: + +```php +use OpenTelemetry\Context\Context; + +$key = Context::createKey('my-key'); +$scope = Context::getCurrent()->with($key, 'value')->activate(); + +//exit without detaching $scope +``` + +This can be problematic in some situations, particularly in legacy applications +which might `exit` or `die`. In that case, active spans are not completed and +exported, and the `DebugScope` complains loudly. + +If you understand why `DebugScope` is complaining and accept the risks, then you +can disable the feature entirely by setting `OTEL_PHP_DEBUG_SCOPES_DISABLED` to +a truthy value. + +### Nested context + +Active context executions can be nested. This is how traces can have nested +spans: + +```php +use OpenTelemetry\Context\Context; + +$key = Context::createKey('my-key'); + +var_dump(Context::getCurrent()->get($key)); //NULL +$scope2 = Context::getCurrent()->with($key, 'context 2')->activate(); +var_dump(Context::getCurrent()->get($key)); //'context 2' +$scope3 = Context::getCurrent()->with($key, 'context 3')->activate(); +var_dump(Context::getCurrent()->get($key)); //'context 3' + +$scope3->detach(); //context 2 is active +$scope2->detach(); //original context is active +var_dump(Context::getCurrent()->get($key)); //NULL +``` + +### Context in asynchronous environments + +For asynchronous PHP programming, for example `Swoole` or the Fiber-based +`Revolt` event loop, there can be multiple active contexts, but still only one +active context per execution context. + +For fiber-based implementations, `Context` is associated with the active fiber, +and forks, switches and is destroyed as appropriate by hooking into PHP's fiber +initialization, forking, and destruction handlers. + +For other async implementations, custom context storage might be needed to +interoperate correctly. Check the [registry](/ecosystem/registry/?language=php) +for storage implementations.