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