Das Konzept der Graphmodul-Services erlaubt es Graphmodulen, zusätzliche Funktionalität, die über die Graph-API aufgerufen werden kann, zur Verfügung zu stellen.
Für den API-Aufruf service(string $graphmodulservice_name, mixed ...$parameter): mixed
ist dabei nur festgelegt, dass der erste Parameter
der nach dem Schema <graphmodul>.<service>
zusammengesetzte Name des
Service ist.
Alle weiteren Parameter und der Rückgabewert können für Services
individuell festgelegt werden.
Services können sowohl dafür verwendet werden, als Teil der öffentlichen API eines Graphmoduls allen Aktionfunktionen und Skripten zur Verfügung zu stehen, als auch dafür, wiederkehrende Aufgaben innerhalb eines Graphmoduls (oder einiger eng zusammenhängender Graphmodule) zu kapseln. Diese Unterscheidung ist dabei aber eine rein konzeptionelle und nicht in allen Fällen eine durch Code erzwungene.
Beispiele für allen Funktionen zur Verfügung stehende Services sind die
weiter unten genauer beschriebenen zeit.aktuell
, graph.remote
,
lexikon.telnorm
, uebersetzung.attribut
und controlpi.send
.
Beispiele für nur intern zu verwendende Services sind graph.pdo
, das nur
innerhalb des graph
-Moduls einen direkten Zugriff auf die Datenbank
erlaubt, oder graphx.dfkritik
, graphx.afkritik
und graphx.fmtkritik
,
die nur in den Graphmodulen graphx
und auto
für das Kritisieren von
Daten- und Aktionfunktionen verwendet werden, für andere Graphmodule und
Skripte aber wenig Nutzen bieten.
Services bekommen bei ihrem Aufruf eine
ConnectionInterface
-Implementierung, die eine Verbindung zum lokalen
Graphen ermöglicht.
Diese sollte aber nur sparsam verwendet werden, da diese Aufrufe der
Graph-API für jegliche Analysen im Modul graphx
unsichtbar sind, die
Verwendung von Attributknoten, Verknüpfungen und Aktionfunktionen durch
Services also z.B. eventuell nicht bei Änderungen oder Entfernen dieser
Strukturen berücksichtigt wird.
Insbesondere schreibende Zugriffe auf die Graph-API sollten nicht stattfinden.
Der service
-API-Aufruf ist nur in Aktion-, nicht in Datenfunktionen
erlaubt.
Zusätzliche Einschränkungen für spezielle Services sind möglich.
So stellt die Funktionalität zum Kritisieren von Aktionfunktionen
beispielsweise sicher, dass der Service graph.pdo
nur innerhalb des
Graphmoduls graph
verwendet wird, da direkte Zugriffe auf die Datenbank
nur dort erforderlich und erlaubt sind.
Wenn ein Service Objekte als Parameter bekommen soll oder als Rückgabewert
liefert, die nicht serialisiert werden können, muss sein lokal
-Attribut
auf ja
gesetzt werden.
Solche Services dürfen dann nicht über Remote-Connections aufgerufen
werden.
zeit.aktuell
zeit.aktuell
ist ein Service, der eine einheitliche Möglichkeit bereit
stellt, den aktuellen Zeitpunkt in unterschiedlichen Formaten zu ermitteln.
Er soll möglichst alle direkten Aufrufe der PHP-Funktion date()
bzw. der
entsprechenden Klasse(n) ersetzen.
Wenn kein Parameter gegeben ist, werden Datum und Zeit sekundengenau
zurückgegeben, was unsere häufigste Art von Zeitstempeln ist.
Wenn ein Parameter gegeben ist, kann dies entweder ein Format-String sein,
wie er von PHP (und anderen Datums-Anwendungen) üblicherweise verwendet
wird, oder eines der Schlüsselwörter jahr
, quartal
, monat
, woche
,
tag
, stunde
, minute
, sekunde
oder wochentag
, um diese Genauigkeit
für den aktuellen Zeitpunkt zurückzugeben.
Der Service verwendet eine am singulären Knoten zeit
konfigurierte Uhr
bzw. Zeitzone oder Europe\Berlin
als Fallback, falls dort keine
konfiguriert ist.
Dies ist die einzige Abhängigkeit von Daten im Graphen.
graph.remote
graph.remote
ist ein Service, um eine im lokalen Graphen konfigurierte
Remote-Graph-Connection zur Verfügung zu stellen.
Der einzige Parameter ist der Name der entsprechenden
remotegraph
-Instanz.
Dies ist ein Beispiel für einen lokalen Graphmodul-Service, da die zurückgegebene Graph-Connection nicht serialisiert werden kann und nur im Kontext des lokalen Graphen Sinn ergibt.
lexikon.telnorm
lexikon.telnorm
ist ein Service zum Normalisieren von Telefonnummern.
Die Ausgabe ist eine mit +
beginnende standardkonforme, internationale
Telefonnummer.
Die Eingabe kann eine bereits normalisierte Telefonnummer oder eine lokale
Telefonnummer, eventuell mit Orts- oder Landesvorwahl sein.
Der erste Parameter ist die zu normalisierende Nummer.
Die Parameter 2 bis 5 sind die internationale Verkehrsausscheidungsziffer,
die Landeskennzahl, die nationale Verkehrsausscheidungsziffer und die
Ortskennzahl.
Wenn diese nicht gegeben sind, wird die Konfiguration aus den singulären
Knoten lexikon
genommen.
Dies ist die einzige Abhängigkeit von Daten im Graphen.
uebersetzung.attribut
uebersetzung.attribut
ist ein Service zum Auslesen der Übersetzungen von
Instanz-Attributen im Modul uebersetzung
.
Die Parameter, die alle gegeben sein müssen, sind die GUID der Instanz, der
zu übersetzende Attributknoten und die Sprache, in die übersetzt werden
soll.
controlpi.send
controlpi.send
ermöglicht es, in Graphen, in denen das Modul controlpi
installiert ist, Nachrichten an konfigurierte coRoot-Instanzen zu schicken.
Der erste Parameter ist der Name der im lokalen Graphen konfigurierten
coRoot-Instanz.
Der zweite Parameter ist die zu sendende Nachricht als eventuell komplexes
PHP-Objekt, das vor dem Senden in JSON kodiert wird.
Unter welchem Schlüssel ein Service aufgerufen werden kann, wird in der
graphmodulservice
-Instanz im Graphen selbst festgelegt.
In dieser wird auch eine PHP-Klasse konfiguriert, die die eigentliche
Implementierung des Services enthält.
Beispielsweise ist für den Service controlpi.send
die Klasse
Graphit\Graph\Module\ControlPi\CoService
angegeben.
Diese Klasse muss für den Graphen zur Laufzeit bekannt sein.
Der Graph sammelt alle Composer-Konfigurations-Dateien der Form
module/*/graph/composer-graph.json
auf.
Wir können also im jeweiligen Modul-Verzeichnis diese Klassen zur Verfügung
stellen.
Für unser Beispiel ist der Inhalt von module/co/graph/composer-graph.json
der folgende:
{
"autoload": {
"psr-4": {
"Graphit\\Graph\\Module\\ControlPi\\": "src/"
}
},
"require": {
"textalk/websocket": "^1.5",
"graphit/giwt-transitional": "^0.1.0 || dev-master"
}
}
Es wird also nicht nur festgelegt, wo nach Implementierungen für Klassen in einem bestimmten Namespace gesucht werden soll, sondern auch welche eventuellen zusätzlichen Abhängigkeiten – in unserem Fall eine Bibliothek für Websockets, da diese im ControlPi-System zur Kommunikation genutzt werden – für diesen Service benötigt werden.
Eine solche Klasse implementiert das Interface
Graphit\Graph\ServiceInterface
, das in der Datei
src/ServiceInterface.php
im Paket graphit/graph-server
zu finden ist.
Dieses Interface legt ausschließlich fest, dass es eine Methode public function call(ConnectionInterface $gc, ...$parameter)
geben muss.
Für unser Beispiel sieht diese Implementierung folgendermaßen aus:
<?php
namespace Graphit\Graph\Module\ControlPi;
use Graphit\Graph\ServiceInterface;
use Graphit\Graph\Common\ConnectionInterface;
use WebSocket\Client;
use WebSocket\ConnectionException;
class CoService implements ServiceInterface
{
public function call(ConnectionInterface $gc, ...$parameter)
{
// Process parameters:
if (!$parameter || !$parameter[0] || !$parameter[1]) {
return false;
}
$coroot_guid = $gc->attributsknoten('coroot_name', $parameter[0]);
if (!$coroot_guid) {
return false;
}
$coroot_url = $gc->attribut($coroot_guid, 'coroot_url');
if (!$coroot_url) {
return false;
}
$message = json_encode($parameter[1]);
// Connect to client:
$client = new Client($coroot_url);
if (!$client) {
echo "Could not connect to Websocket {$coroot_url}.\n";
return false;
}
// Send:
try {
$client->text($message);
$client->close();
return true;
} catch (ConnectionException $e) {
echo "Exception while trying to send through: {$coroot_url}:\n";
var_dump($e);
}
echo "Sending through Websocket failed!\n";
return false;
}
}
Da die Signatur von call()
sehr allgemein gehalten ist, wird zunächst
überprüft, ob wir zumindest die richtige Anzahl an Parametern bekommen
haben.
Dann werden einige Konfigurationen aus dem Graphen ausgelesen, in unserem
Fall die URL der ControlPi-Instanz, an die eine Nachricht gesendet werden
soll, anhand des als Parameter übergebenen Namens dieser Instanz.
Dies macht die Benutzung des Service komfortabler, da sonst jede
Aktionfunktion und jedes Skript dieses Ermitteln der URL selbt vornehmen
müsste.
Schließlich wird mit Hilfe der oben als Abhängigkeit konfigurierten
Websocket-Bibliothek die als Parameter übergebene Nachricht an die
ControlPi-Instanz gesandt.