Context Factories¶
The Context Library has a concept of Context Factory (a precise name
should actually be Context Manager Factory). A Context Factory is an object
which takes a value and returns a new instance of
ContextManagerInterface
, appropriate for
given value, or null
if it won’t handle the value. For example, the
DefaultContextFactory
creates
ResourceContextManager
for any
PHP resource and TrivialValueWrapper
for any
other value (except for values that are already instances of
ContextManagerInterface
).
The Context Library has a single stack of (custom) Context Factories
(ContextFactoryStack
). It’s empty by
default, so initially only the DefaultContextFactory
is used to generate Context Managers. A custom factory object can be pushed
to the top of the stack to get precedence over other factories.
Creating custom Context Factories¶
A simplest way to create new Context Factory is to extend the
AbstractManagedContextFactory
. The new
context factory must implement the
getContextManager()
method. The AbstractManagedContextFactory
is
either a Context Factory and Context Manager. When an instance of
AbstractManagedContextFactory
is passed
to with()
, it gets pushed to the top of
ContextFactoryStack
when entering context
and popped when exiting (so the new Context Factory works for all nested
contexts).
Example with custom Managed Context Factory¶
In the following example we’ll wrap an integer value with an object named
MyCounter
. Then, we’ll create a dedicated Context Manager, named
MyCounterManger
, to increment the counter when entering a context and
decrement when exiting. Finally, we’ll provide Context Factory named
MyContextFactory
to recognize MyCounter
objects and wrap them with
MyCounterManager
.
For the purpose of example we need the following symbols to be imported
1 2 3 | use function Korowai\Lib\Context\with;
use Korowai\Lib\Context\AbstractManagedContextFactory;
use Korowai\Lib\Context\ContextManagerInterface;
|
Our counter class will be as simple as
1 2 3 4 5 6 7 8 9 | class MyCounter
{
public $value;
public function __construct(int $value)
{
$this->value = $value;
}
}
|
The counter manager shall just increment/decrement counter’s value and print short messages when entering/exiting a context.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class MyCounterManager implements ContextManagerInterface
{
public $counter;
public function __construct(MyCounter $counter)
{
$this->counter = $counter;
}
public function enterContext()
{
$this->counter->value ++;
print("MyCounterManager::enterContext()\n");
return $this->counter;
}
public function exitContext(?\Throwable $exception = null) : bool
{
$this->counter->value --;
print("MyCounterManager::exitContext()\n");
return false;
}
}
|
Finally, comes the Context Factory.
1 2 3 4 5 6 7 8 9 10 | class MyContextFactory extends AbstractManagedContextFactory
{
public function getContextManager($arg) : ?ContextManagerInterface
{
if($arg instanceof MyCounter) {
return new MyCounterManager($arg);
}
return null;
}
}
|
We can now push an instance of MyContextFactory
to the factory stack. To
push it temporarily, we’ll create two nested contexts (outer and inner),
pass an instance of MyContextFactory
to the outer context and do actual job
in the inner context.
1 2 3 4 5 6 7 | with(new MyContextFactory(), new MyCounter(0))(function ($cf, $cnt) {
echo "before: " . $cnt->value . "\n";
with($cnt)(function ($cnt) {
echo "in: " . $cnt->value . "\n";
});
echo "after: " . $cnt->value . "\n";
});
|
It must be clear, that MyContextFactory
is inactive in the outer
with()
(line 1). It only works when entering/exiting inner contexts (line 3
in the above snippet).
Following is the output from our example
1 2 3 4 5 | before: 0
MyCounterManager::enterContext()
in: 1
MyCounterManager::exitContext()
after: 0
|