Context: Ausführungskontexte
Warum das gebraucht wird
Es gibt eine API mit einer Service-Klasse, die Aktionen durchführen muss, die an ein Autorisierungstoken gebunden sind.
Das Token jedoch an jede Methode des Service zu übergeben, ist eine schlechte Idee.
In PHP wird dieses Problem durch globale Variablen oder statische Klasseneigenschaften gelöst.
Aber in einer asynchronen Umgebung, in der ein einzelner Prozess verschiedene Anfragen bearbeiten kann, funktioniert dieser Ansatz nicht,
da zum Zeitpunkt des Aufrufs nicht bekannt ist, welche Anfrage gerade bearbeitet wird.
Async\Context ermöglicht es, Daten zu speichern, die mit einer Coroutine oder einem Scope verknüpft sind, und die Anwendungslogik
auf Basis des Ausführungskontexts aufzubauen.
Was ist Context
Async\Context ist ein Key-Value-Speicher, der an einen Scope oder eine Coroutine gebunden ist.
Kontexte bilden eine Hierarchie: Beim Lesen eines Werts wird in der Scope-Hierarchie nach oben gesucht.
Dies ist analog zu context.Context in Go oder CoroutineContext in Kotlin.
Ein Mechanismus zur Datenübergabe durch die Hierarchie, ohne Parameter explizit weiterzureichen.
Drei Ebenen des Kontexts
TrueAsync bietet drei Funktionen für den Zugriff auf Kontexte:
<?php
use function Async\current_context;
use function Async\coroutine_context;
use function Async\root_context;
// Kontext des aktuellen Scopes
$scopeCtx = current_context();
// Kontext der aktuellen Coroutine
$coroCtx = coroutine_context();
// Globaler Wurzelkontext
$rootCtx = root_context();
?>
current_context()
Gibt den Kontext des aktuellen Scope zurück. Falls der Kontext noch nicht erstellt wurde, wird er automatisch erzeugt.
Hier gesetzte Werte sind für alle Coroutinen in diesem Scope sichtbar.
coroutine_context()
Gibt den Kontext der aktuellen Coroutine zurück. Dies ist ein privater Kontext, der nur dieser Coroutine gehört. Andere Coroutinen können hier gesetzte Daten nicht sehen.
root_context()
Gibt den globalen Kontext zurück, der über die gesamte Anfrage hinweg geteilt wird. Hier gesetzte Werte sind über find() aus jedem Kontext sichtbar.
Schlüssel
Ein Schlüssel kann ein String oder ein Objekt sein:
<?php
use function Async\current_context;
$ctx = current_context();
// String-Schlüssel
$ctx->set('request_id', 'abc-123');
// Objekt als Schlüssel (nützlich für eindeutige Token)
$key = new stdClass();
$ctx->set($key, 'value');
?>
Objekt-Schlüssel werden als Referenz im Kontext gespeichert, was ihre Eindeutigkeit garantiert.
Lesen: Lokal und hierarchisch
find() / get() / has() – Hierarchische Suche
Sucht einen Wert zuerst im aktuellen Kontext, dann im übergeordneten, und so weiter bis zum Wurzelkontext:
<?php
use function Async\root_context;
use function Async\current_context;
use function Async\spawn;
use function Async\await;
root_context()->set('app_name', 'MyApp');
$scope = new Async\Scope();
spawn(function() {
// find() sucht in der Hierarchie nach oben
$name = current_context()->find('app_name');
echo $name; // "MyApp" -- im root_context gefunden
});
?>
findLocal() / getLocal() / hasLocal() – Nur im aktuellen Kontext
Sucht einen Wert nur im aktuellen Kontext, ohne in der Hierarchie aufzusteigen:
<?php
use function Async\root_context;
use function Async\current_context;
root_context()->set('app_name', 'MyApp');
$local = current_context()->findLocal('app_name');
// null -- dieser Wert ist im aktuellen Scope nicht gesetzt
$inherited = current_context()->find('app_name');
// "MyApp" -- im übergeordneten Scope gefunden
?>
Schreiben und Löschen
set()
<?php
$ctx = current_context();
// Wert setzen (Standard replace = false)
$ctx->set('key', 'value');
// Wiederholtes set ohne replace -- Fehler
$ctx->set('key', 'new_value'); // Fehler: A context key already exists
// Mit explizitem replace = true
$ctx->set('key', 'new_value', replace: true); // OK
Die Methode set() gibt $this zurück und ermöglicht Method-Chaining:
<?php
current_context()
->set('user_id', 42)
->set('request_id', 'abc-123')
->set('locale', 'en');
?>
unset()
<?php
$ctx = current_context();
$ctx->unset('key');
Die Methode unset() gibt ebenfalls $this zurück.
Praktische Beispiele
Übergabe einer Request-ID
<?php
use function Async\spawn;
use function Async\await;
use function Async\current_context;
// Middleware setzt die request_id
current_context()->set('request_id', bin2hex(random_bytes(8)));
// Jede Coroutine in diesem Scope kann sie lesen
spawn(function() {
$requestId = current_context()->find('request_id');
// Verwendung im Logging
error_log("[$requestId] Verarbeite Anfrage...");
});
?>
Coroutine-Kontext als privater Speicher
<?php
use function Async\spawn;
use function Async\await;
use function Async\coroutine_context;
$c1 = spawn(function() {
coroutine_context()->set('step', 1);
// ... Arbeit ausführen
$step = coroutine_context()->getLocal('step');
});
$c2 = spawn(function() {
// Kann 'step' von c1 nicht sehen
$step = coroutine_context()->findLocal('step'); // null
});
?>
Konfiguration über root_context
<?php
use function Async\root_context;
use function Async\current_context;
use function Async\spawn;
// Am Anfang der Anfrage setzen
root_context()
->set('db_host', 'localhost')
->set('cache_ttl', 3600);
// Von jeder Coroutine aus verfügbar
spawn(function() {
$dbHost = current_context()->find('db_host'); // "localhost"
});
?>
Siehe auch
- Scope – Verwaltung der Coroutine-Lebenszeiten
- Coroutines – die grundlegende Einheit der Nebenläufigkeit
- current_context() – Abrufen des Kontexts des aktuellen Scopes