44
55namespace OpenTelemetry \Context ;
66
7- use function assert ;
7+ use function spl_object_id ;
88
99/**
10- * @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.6.1 /specification/context/context .md#overview
10+ * @see https://github.com/open-telemetry/opentelemetry-specification/blob/main /specification/context/README .md#context
1111 */
1212final class Context
1313{
14- /**
15- * @var ContextStorageInterface&ExecutionContextAwareInterface
16- */
14+ /** @var ContextStorageInterface&ExecutionContextAwareInterface */
1715 private static ContextStorageInterface $ storage ;
18- private static ?self $ root = null ;
19- private ?ContextKey $ key ;
20- /**
21- * @var mixed
22- */
23- private $ value ;
24- private ?self $ parent ;
2516
26- /**
27- * This is a general purpose read-only key-value store. Read-only in the sense that adding a new value does not
28- * mutate the existing context, but returns a new Context which has the new value added.
29- *
30- * In practical terms, this is implemented as a linked list of Context instances, with each one holding a reference
31- * to the key object, the value that corresponds to the key, and an optional reference to the parent Context
32- * (i.e. the next link in the linked list chain)
33- *
34- * @param ContextKey|null $key The key object. Should only be null when creating an "empty" context
35- * @param mixed $value
36- * @param Context|null $parent Reference to the parent object
37- */
38- private function __construct (?ContextKey $ key =null , $ value =null , ?self $ parent =null )
17+ // Optimization for spans to avoid copying the context array.
18+ private static ContextKey $ spanContextKey ;
19+ private ?object $ span = null ;
20+ /** @var array<int, mixed> */
21+ private array $ context = [];
22+ /** @var array<int, ContextKey> */
23+ private array $ contextKeys = [];
24+
25+ private function __construct ()
3926 {
40- $ this ->key = $ key ;
41- $ this ->value = $ value ;
42- $ this ->parent = $ parent ;
27+ self ::$ spanContextKey = ContextKeys::span ();
4328 }
4429
45- /**
46- * Static version of get()
47- * This is primarily useful when the caller doesn't already have a reference to the Context that they want to mutate.
48- * This will operate on the "current" global context in that scenario.
49- *
50- * There are two ways to call this function:
51- * 1) With a $context value:
52- * Context::getValue($key, $context) is functionally equivalent to $context->get($key)
53- * 2) Without a $context value:
54- * This will fetch the "current" Context if one exists or create one if not, then attempt to get the value from it.
55- *
56- * @param ContextKey $key
57- * @param Context|null $context
58- *
59- * @return mixed
60- */
61- public static function getValue (ContextKey $ key , ?self $ context =null )
30+ public static function createKey (string $ key ): ContextKey
6231 {
63- $ context ??= self ::getCurrent ();
64-
65- return $ context ->get ($ key );
32+ return new ContextKey ($ key );
6633 }
6734
6835 /**
69- * This adds a key/value pair to this Context. We do this by instantiating a new Context instance with the key/value and pass
70- * a reference to $this as the "parent" creating the linked list chain.
71- *
72- * @param ContextKey $key
73- * @param mixed $value
36+ * @internal
7437 *
75- * @return Context a new Context containing the key/value
38+ * @param ContextStorageInterface&ExecutionContextAwareInterface $storage
7639 */
77- public function with ( ContextKey $ key , $ value ): self
40+ public static function setStorage ( ContextStorageInterface $ storage ): void
7841 {
79- return new self ( $ key , $ value , $ this ) ;
42+ self :: $ storage = $ storage ;
8043 }
8144
8245 /**
83- * @todo: Implement this on the API side
46+ * @return ContextStorageInterface&ExecutionContextAwareInterface
8447 */
85- public function withContextValue ( ImplicitContextKeyedInterface $ value ): self
48+ public static function storage ( ): ContextStorageInterface
8649 {
87- return $ value ->storeInContext ($ this );
50+ /** @psalm-suppress RedundantPropertyInitializationCheck */
51+ return self ::$ storage ??= new ContextStorage ();
8852 }
8953
9054 /**
91- * Makes `$this` the currently active {@see Context}.
92- *
93- * @todo: Implement this on the API side
55+ * @internal
9456 */
57+ public static function getRoot (): Context
58+ {
59+ static $ empty ;
60+
61+ return $ empty ??= new self ();
62+ }
63+
64+ public static function getCurrent (): Context
65+ {
66+ return self ::storage ()->current ();
67+ }
68+
9569 public function activate (): ScopeInterface
9670 {
9771 $ scope = self ::storage ()->attach ($ this );
@@ -101,58 +75,45 @@ public function activate(): ScopeInterface
10175 return $ scope ;
10276 }
10377
104- /**
105- * Fetch a value from the Context given a key value.
106- *
107- * @return mixed
108- */
109- public function get (ContextKey $ key )
78+ public function withContextValue (ImplicitContextKeyedInterface $ value ): Context
11079 {
111- for ($ context = $ this ; $ context ; $ context = $ context ->parent ) {
112- if ($ context ->key === $ key ) {
113- return $ context ->value ;
114- }
115- }
116-
117- return null ;
80+ return $ value ->storeInContext ($ this );
11881 }
11982
120- /**
121- * @internal
122- *
123- * @param ContextStorageInterface&ExecutionContextAwareInterface $storage
124- */
125- public static function setStorage (ContextStorageInterface $ storage ): void
83+ public function with (ContextKey $ key , $ value ): self
12684 {
127- self ::$ storage = $ storage ;
128- }
85+ if ($ this ->get ($ key ) === $ value ) {
86+ return $ this ;
87+ }
12988
130- /**
131- * @return ContextStorageInterface&ExecutionContextAwareInterface
132- */
133- public static function storage (): ContextStorageInterface
134- {
135- /** @psalm-suppress RedundantPropertyInitializationCheck */
136- return self ::$ storage ??= new ContextStorage ();
137- }
89+ $ self = clone $ this ;
13890
139- /**
140- * @param non-empty-string $key
141- *
142- * @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.6.1/specification/context/context.md#create-a-key
143- */
144- public static function createKey (string $ key ): ContextKey
145- {
146- return new ContextKey ($ key );
147- }
91+ if ($ key === self ::$ spanContextKey ) {
92+ $ self ->span = $ value ; // @phan-suppress-current-line PhanTypeMismatchPropertyReal
14893
149- public static function getCurrent (): self
150- {
151- return self ::storage ()->current ();
94+ return $ self ;
95+ }
96+
97+ $ id = spl_object_id ($ key );
98+ if ($ value !== null ) {
99+ $ self ->context [$ id ] = $ value ;
100+ $ self ->contextKeys [$ id ] ??= $ key ;
101+ } else {
102+ unset(
103+ $ self ->context [$ id ],
104+ $ self ->contextKeys [$ id ],
105+ );
106+ }
107+
108+ return $ self ;
152109 }
153110
154- public static function getRoot (): self
111+ public function get ( ContextKey $ key )
155112 {
156- return self ::$ root ??= new self ();
113+ if ($ key === self ::$ spanContextKey ) {
114+ return $ this ->span ;
115+ }
116+
117+ return $ this ->context [spl_object_id ($ key )] ?? null ;
157118 }
158119}
0 commit comments