Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 61872b5

Browse files
authored
Implement generic context (#60)
* Add generic Context class * Context can fetch single values * Remove Context::setCurrent * Add @internal annotation for Context::reset() * Add ability to set multiple context values at once
1 parent 825db7f commit 61872b5

File tree

2 files changed

+310
-0
lines changed

2 files changed

+310
-0
lines changed

src/Core/Context.php

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
/**
3+
* Copyright 2017 OpenCensus Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace OpenCensus\Core;
19+
20+
/**
21+
* This class is an implementation of a generic context.
22+
*
23+
* Example:
24+
* ```
25+
* // Create and set a new context, which inherits values from the current
26+
* // context and adds a new key/value pair of 'foo'/'bar'.
27+
* $prev = Context::current()->withValue('foo', 'bar')->attach();
28+
* try {
29+
* // Do something within the context
30+
* } finally {
31+
* // This makes sure that $prev will be current after this execution
32+
* Context::current()->detach($prev);
33+
* }
34+
* // Here, we are 100% sure $prev is the current context.
35+
* ```
36+
*/
37+
class Context
38+
{
39+
/**
40+
* @var Context
41+
*/
42+
private static $current;
43+
44+
/**
45+
* @var array
46+
*/
47+
private $values;
48+
49+
/**
50+
* Creates a new Context.
51+
*
52+
* @param array $initialValues
53+
*/
54+
public function __construct($initialValues = [])
55+
{
56+
$this->values = $initialValues;
57+
}
58+
59+
/**
60+
* Creates a new context with the given key/value set.
61+
*
62+
* @param string $key
63+
* @param mixed $value
64+
* @return Context
65+
*/
66+
public function withValue($key, $value)
67+
{
68+
$copy = $this->values;
69+
$copy[$key] = $value;
70+
return new Context($copy);
71+
}
72+
73+
/**
74+
* Creates a new context with the given key/values.
75+
*
76+
* @param array $data
77+
* @return Context
78+
*/
79+
public function withValues($data)
80+
{
81+
$copy = $this->values;
82+
foreach ($data as $key => $value) {
83+
$copy[$key] = $value;
84+
}
85+
return new Context($copy);
86+
}
87+
88+
/**
89+
* Fetches the value for a given key in this context. Returns the provided
90+
* default if not set.
91+
*
92+
* @param string $key
93+
* @param mixed $default [optional]
94+
* @return mixed
95+
*/
96+
public function value($key, $default = null)
97+
{
98+
return array_key_exists($key, $this->values)
99+
? $this->values[$key]
100+
: $default;
101+
}
102+
103+
/**
104+
* Attaches this context, thus entering a new scope within which this
105+
* context is current(). The previously current context is returned.
106+
*
107+
* @return Context
108+
*/
109+
public function attach()
110+
{
111+
$current = self::current();
112+
self::$current = $this;
113+
return $current;
114+
}
115+
116+
/**
117+
* Reverses an attach(), restoring the previous context and exiting the
118+
* current scope.
119+
*
120+
* @param Context $toAttach
121+
*/
122+
public function detach(Context $toAttach)
123+
{
124+
if (self::current() !== $this) {
125+
trigger_error('Unexpected context to detach.', E_USER_WARNING);
126+
}
127+
128+
self::$current = $toAttach;
129+
}
130+
131+
/**
132+
* Returns all the contained data.
133+
*
134+
* @return array
135+
*/
136+
public function values()
137+
{
138+
return $this->values;
139+
}
140+
141+
/**
142+
* Returns the context associated with the current scope, will never return
143+
* null.
144+
*
145+
* @return Context
146+
*/
147+
public static function current()
148+
{
149+
if (!self::$current) {
150+
self::$current = self::background();
151+
}
152+
153+
return self::$current;
154+
}
155+
156+
/**
157+
* Returns an empty context.
158+
*
159+
* @return Context
160+
*/
161+
public static function background()
162+
{
163+
return new Context();
164+
}
165+
166+
/**
167+
* Resets the context to an initial state. This is generally used only for
168+
* testing.
169+
*
170+
* @internal
171+
*/
172+
public static function reset()
173+
{
174+
self::$current = null;
175+
}
176+
}

tests/unit/Core/ContextTest.php

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
/**
3+
* Copyright 2017 OpenCensus Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace OpenCensus\Tests\Unit\Core;
19+
20+
use OpenCensus\Core\Context;
21+
22+
/**
23+
* @group core
24+
*/
25+
class ContextTest extends \PHPUnit_Framework_TestCase
26+
{
27+
public function setUp()
28+
{
29+
Context::reset();
30+
}
31+
32+
public function tearDown()
33+
{
34+
Context::reset();
35+
}
36+
37+
public function testBackgroundGeneratesEmptyContext()
38+
{
39+
$context = Context::background();
40+
$this->assertInstanceOf(Context::class, $context);
41+
$this->assertEquals([], $context->values());
42+
}
43+
44+
public function testCurrentAlwaysReturnsAContext()
45+
{
46+
$context = Context::current();
47+
$this->assertInstanceOf(Context::class, $context);
48+
}
49+
50+
public function testAttachingCurrentContext()
51+
{
52+
$context = new Context(['foo' => 'bar']);
53+
$prevContext = $context->attach();
54+
55+
$current = Context::current();
56+
$this->assertInstanceOf(Context::class, $current);
57+
$this->assertEquals($context, $current);
58+
}
59+
60+
public function testRestoringCurrentContext()
61+
{
62+
$context = new Context(['foo' => 'bar']);
63+
$prevContext = $context->attach();
64+
65+
Context::current()->detach($prevContext);
66+
}
67+
68+
/**
69+
* @expectedException PHPUnit_Framework_Error_Warning
70+
*/
71+
public function testRestoringCurrentContextRequiresSameObject()
72+
{
73+
$context = new Context(['foo' => 'bar']);
74+
$prevContext = $context->attach();
75+
76+
$other = new Context(['foo' => 'bar']);
77+
$this->assertFalse($prevContext === $other);
78+
79+
$other->detach($other);
80+
}
81+
82+
public function testContextValuesAreCopied()
83+
{
84+
$data = ['foo' => 'bar'];
85+
$context = new Context($data);
86+
$data['foo'] = 'asdf';
87+
88+
$this->assertNotEquals($data, $context->values());
89+
}
90+
91+
public function testWithValueInheritsPreviousContext()
92+
{
93+
$context = new Context(['foo' => 'bar']);
94+
$newContext = $context->withValue('asdf', 'qwer');
95+
$this->assertEquals(['foo' => 'bar', 'asdf' => 'qwer'], $newContext->values());
96+
}
97+
98+
public function testChangingExistingValueDoesNotAffectOtherContexts()
99+
{
100+
$context = new Context(['foo' => 'bar']);
101+
$newContext = $context->withValue('foo', 'baz');
102+
$this->assertEquals(['foo' => 'baz'], $newContext->values());
103+
$this->assertEquals(['foo' => 'bar'], $context->values());
104+
}
105+
106+
public function testFetchingExistingValue()
107+
{
108+
$context = new Context(['foo' => 'bar']);
109+
$this->assertEquals('bar', $context->value('foo'));
110+
}
111+
112+
public function testFetchingNonExistentValue()
113+
{
114+
$context = new Context();
115+
$this->assertNull($context->value('foo'));
116+
}
117+
118+
public function testFetchingNonExistentValueWithDefault()
119+
{
120+
$default = 'bar';
121+
$context = new Context();
122+
$this->assertEquals($default, $context->value('foo', $default));
123+
}
124+
125+
public function testApplyMultipleValues()
126+
{
127+
$context = new Context(['foo' => 'bar']);
128+
$newContext = $context->withValues([
129+
'asdf' => 'qwer',
130+
'zxcv' => 'jkl;'
131+
]);
132+
$this->assertEquals(['foo' => 'bar', 'asdf' => 'qwer', 'zxcv' => 'jkl;'], $newContext->values());
133+
}
134+
}

0 commit comments

Comments
 (0)