phasync
Context switching / Service lifetimes: Singleton, Scoped, Ephemeral

Context switching

Service lifetimes: Singleton, Scoped, Ephemeral

A service lives for the worker, for one request, or for one use. Choose by whose data it holds.

Dependency-injection containers name three lifetimes. Under PHP-FPM a singleton lived as long as the request, so the first two were the same. In a worker they are not.

Lifetime Lives for In PHP Typical services
Singleton the worker a static, a container singleton, an object built in swerve.php configuration, the router's table, a logger, a template engine, a connection pool
Scoped one request a WeakMap keyed by Scope::getCurrent() the current user, the request, the session, a unit of work, a per-request cache
Ephemeral one use new, a factory value objects, query builders, DTOs

Choose by whose data the service holds. If it holds nothing but what it was built with, it is a singleton. If it holds anything that belongs to a request, it is scoped.

A scope helper#

phasync has no scope container. The pattern from per-request state is short enough to write once for your own services:

final class Scope
{
    /** @var WeakMap<object, ArrayObject> */
    private static WeakMap $scopes;

    /** The instance of $class that belongs to the current request; $make creates it on first use. */
    public static function get(string $class, ?Closure $make = null): object
    {
        self::$scopes ??= new WeakMap();
        $scope          = self::$scopes[Scope::getCurrent()] ??= new ArrayObject();

        return $scope[$class] ??= ($make ?? throw new LogicException("No $class in this request"))();
    }
}

Scope::get(CurrentUser::class, fn () => new CurrentUser('ann'));   // at the start of the request
Scope::get(CurrentUser::class)->name;                              // anywhere later in it

In the frameworks#

Each framework spells the lifetimes its own way: Laravel's scoped(), Symfony's services and kernel.reset, Yii's reset callbacks. What the adapter does with them is on the framework's page: Laravel, Symfony, Yii.

Previous: Per-request state ยท Next: Captive dependencies