Hey.
Currently, if a user wants to store custom information in the context, they either need to:
- extend the
Context class that graphqlite provides - but this breaks as soon as two separate implementations want to extend the Context
- extend the
ContextInterface interface that graphqlite provides, wrap the original one and delegate all property access and method calls to the original one - but this also breaks as soon as someone attempts to type-hint it, and the implementation would be quite verbose and unstable
Instead, I propose we make Context a little more universal and a little easier to use:
class Context implements ContextInterface, ResetableContextInterface
{
/** @var SplObjectStorage<object, mixed> */
private SplObjectStorage $data;
public function has(ContextToken $token): bool
{
return isset($this->data[$token]);
}
/**
* @template T
*
* @param ContextToken<T> $token
*
* @return T
*/
public function get(ContextToken $token): mixed
{
if ($this->has($token)) {
return $this->data[$token];
}
$value = ($token->default)();
$this->set($token, $value);
return $value;
}
/**
* @template T
*
* @param ContextToken<T> $token
* @param T $value
*/
public function set(ContextToken $token, mixed $value): mixed
{
return $this->data[$token] = $value;
}
/** @deprecated */
public function getPrefetchBuffer(ParameterInterface $field): PrefetchBuffer
{
static $token;
if (!$token) {
$token = new ContextToken(fn () => new PrefetchBuffer());
}
return $this->get($token);
}
public function reset(): void
{
$this->data = new SplObjectStorage();
}
}
/**
* @template-covariant T
*/
final class ContextToken
{
/**
* @param Closure(): T $default
*/
public function __construct(
public readonly Closure $default,
)
{
}
}
which is basically an array $data, but with type-safety, autocompletion and protection from clashing. It can later be used in userland like so:
/** @return ContextToken<MyPrefetchBuffer> */
function myOwnPrefetchBufferContextToken(): ContextToken {
// PHP 8.3+
static $token = new ContextToken(fn () => new MyPrefetchBuffer());
return $token;
}
// PHPStan correctly infers the type of $buffer as MyPrefetchBuffer
$buffer = $context->get(myOwnPrefetchBufferContextToken());
It could be simplified further when PHP finally allows expressions as constant values:
const PREFETCH_BUFFER_CONTEXT_TOKEN = new ContextToken(fn () => new MyPrefetchBuffer());
// PHPStan correctly infers the type of $buffer as MyPrefetchBuffer
$buffer = $context->get(PREFETCH_BUFFER_CONTEXT_TOKEN);
This isn't a new concept, and is widely used in JS/TS ecosystem. For example, a very similar use case exists in Angular: https://angular.dev/api/common/http/HttpContext#usage-notes . PHP implementation is, admittedly, clunky, but we do get some benefits in return:
- context can be extended without extending the class
- context can be extended from multiple sources, independently
ContextInterface would no longer be needed
It would be perfect to have this in webonyx/graphql, but it's a bigger change for them than it is for graphqlite, so I doubt they'd accept that idea. In case of graphqlite, we can, of course, preserve full backwards compatibility, simply deprecating the interface and getPrefetchBuffer. We could also get rid of reset() in the meantime :)
Hey.
Currently, if a user wants to store custom information in the context, they either need to:
Contextclass thatgraphqliteprovides - but this breaks as soon as two separate implementations want to extend theContextContextInterfaceinterface thatgraphqliteprovides, wrap the original one and delegate all property access and method calls to the original one - but this also breaks as soon as someone attempts to type-hint it, and the implementation would be quite verbose and unstableInstead, I propose we make
Contexta little more universal and a little easier to use:which is basically an
array $data, but with type-safety, autocompletion and protection from clashing. It can later be used in userland like so:It could be simplified further when PHP finally allows expressions as constant values:
This isn't a new concept, and is widely used in JS/TS ecosystem. For example, a very similar use case exists in Angular: https://angular.dev/api/common/http/HttpContext#usage-notes . PHP implementation is, admittedly, clunky, but we do get some benefits in return:
ContextInterfacewould no longer be neededIt would be perfect to have this in
webonyx/graphql, but it's a bigger change for them than it is forgraphqlite, so I doubt they'd accept that idea. In case ofgraphqlite, we can, of course, preserve full backwards compatibility, simply deprecating the interface andgetPrefetchBuffer. We could also get rid ofreset()in the meantime :)