Graph-IT

Graphmodul-Services

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.

Einschränkungen für die Funktionalität und den Aufruf von Services

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.

Beispiele für extern zur Verfügung gestellte Funktionalität

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.

Implementierung von Graphmodul-Services

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.