initial commit
authorSebastian Wassen <sebastian.wassen@graph-it.org>
Fri, 18 Aug 2017 14:06:15 +0000 (16:06 +0200)
committerSebastian Wassen <sebastian.wassen@graph-it.com>
Fri, 18 Aug 2017 14:07:14 +0000 (16:07 +0200)
.gitignore [new file with mode: 0644]
composer.json [new file with mode: 0644]
composer.lock [new file with mode: 0644]
src/Connection.php [new file with mode: 0644]
src/RemoteConnection.php [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..57872d0
--- /dev/null
@@ -0,0 +1 @@
+/vendor/
diff --git a/composer.json b/composer.json
new file mode 100644 (file)
index 0000000..007c143
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "name": "graphit/graph-client",
+  "type": "library",
+  "authors": [
+    {
+      "name": "Graph-IT",
+      "email": "info@graph-it.com"
+    }
+  ],
+  "require": {
+    "graphit/graph-common": "dev-master"
+  },
+  "autoload": {
+    "psr-4": { "Graphit\\Graph\\Client\\": "src/" }
+  }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644 (file)
index 0000000..b45a5bc
--- /dev/null
@@ -0,0 +1,46 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "ea6b729336e3f588b73de24a48480b85",
+    "packages": [
+        {
+            "name": "graphit/graph-common",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "ssh://git@git.graph-it.com/graphit/graph-common",
+                "reference": "96f2657f44cefb3aa5c1de2b2ea95d9411a13098"
+            },
+            "require": {
+                "php": ">=5.6.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Graphit\\Graph\\Common\\": "src/"
+                }
+            },
+            "authors": [
+                {
+                    "name": "Graph-IT",
+                    "email": "info@graph-it.com"
+                }
+            ],
+            "description": "Vom Graphserver und -Client geteilte Sourcen.",
+            "time": "2017-08-18T12:42:07+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {
+        "graphit/graph-common": 20
+    },
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
diff --git a/src/Connection.php b/src/Connection.php
new file mode 100644 (file)
index 0000000..2b4567d
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+
+namespace Graphit\Graph\Client;
+
+use Graphit\Graph\Common\ConnectionInterface;
+
+class Connection extends RemoteConnection implements ConnectionInterface {
+
+  /** @var string */
+  protected $url;
+
+  /** @var resource */
+  protected $socket;
+
+  /** @var string */
+  protected $protocoll;
+
+  /** @var string */
+  protected $cryptoType;
+
+  public function __construct($url, array $options = [])
+  {
+    $this->url = $url;
+    $this->options = $options;
+
+    $matches = [];
+    $pattern = '/^(?<protocoll>[^:]+):\/\//';
+    if (preg_match($pattern, $this->url, $matches) !== 1) {
+      throw new \Exception('Unable to find Transport in URL '.$this->url.'!');
+    }
+    $this->protocoll = $matches['protocoll'];
+
+    $this->cryptoType = false;
+    $cryptoTypes = [
+      'tls'   => STREAM_CRYPTO_METHOD_TLS_CLIENT,
+    ];
+    if (isset($cryptoTypes[$this->protocoll])) {
+      $this->cryptoType = $cryptoTypes[$this->protocoll];
+    }
+  }
+
+  public function __destruct()
+  {
+    if ($this->socket) {
+      fclose($this->socket);
+    }
+  }
+
+  protected function _connect()
+  {
+    $context = stream_context_create();
+
+    if ($this->cryptoType) {
+      stream_context_set_option($context, [
+        'ssl' => [
+          'verify_peer'      => true,
+          'verify_peer_name' => false,
+          'ciphers'          => 'HIGH',
+          'cafile'           => $this->options['cafile'],
+          'local_cert'       => $this->options['lofile'],
+        ],
+      ]);
+    }
+
+    $this->socket = stream_socket_client($this->url, $errno, $errstr, ini_get('default_socket_timeout'), STREAM_CLIENT_CONNECT, $context);
+    if ($this->socket === false) {
+      throw new \Exception("stream_socket_server() failed: $errstr\n");
+    }
+    $this->_readMessage();
+  }
+
+  protected function _readBytes($size) {
+    $bytes = '';
+    while (strlen($bytes) < $size) {
+      $fread = fread($this->socket, $size - strlen($bytes));
+      if ($fread === false) {
+        break;
+      }
+      $bytes.= $fread;
+    }
+    if (strlen($bytes) != $size) {
+      throw new \Exception("Unable to read $size Bytes from the socket");
+    }
+    return $bytes;
+  }
+
+  protected function _readMessage() {
+    $size = $this->_readBytes(4);
+    $size = unpack('Vsize', $size)['size'];
+
+    $message = $this->_readBytes($size);
+    return msgpack_unpack($message);
+  }
+
+  protected function _writeBytes($bytes) {
+    $size = strlen($bytes);
+    for ($written = 0; $written < strlen($bytes); $written += $fwrite) {
+      $fwrite = fwrite($this->socket, substr($bytes, $written));
+      if ($fwrite === false) {
+        break;
+      }
+    }
+    if ($written != $size) {
+      throw new \Exception("Unable to write $size Bytes request to socket");
+    }
+  }
+
+  protected function _writeMessage($message) {
+    $message = msgpack_pack($message);
+    $message = pack('V', strlen($message)).$message;
+    $this->_writeBytes($message);
+  }
+
+  /** */
+  protected function call($method, array $params)
+  {
+    static $id = 0;
+
+    if ( !$this->socket) {
+      $this->_connect();
+    }
+
+    $request = [
+      'jsonrpc' => '2.0',
+      'method' => $method,
+      'params' => $params,
+      'id' => ++$id,
+    ];
+    $this->_writeMessage($request);
+
+    $response = $this->_readMessage();
+    if ( !isset($response['jsonrpc']) || $response['jsonrpc'] != $request['jsonrpc']) {
+      throw new \Exception('Not a JSON-RPC 2.0 response!');
+    }
+    if (isset($response['error'])) {
+      $error = implode(' ', $response['error']);
+      throw new \Exception("JSON-RPC: Remote error: {$error}");
+    }
+    if ( !isset($response['id']) || $response['id'] != $request['id']) {
+      throw new \Exception('JSON-RPC id missing or invalid');
+    }
+
+    return $response['result'];
+  }
+}
diff --git a/src/RemoteConnection.php b/src/RemoteConnection.php
new file mode 100644 (file)
index 0000000..4f9e798
--- /dev/null
@@ -0,0 +1,183 @@
+<?php
+
+namespace Graphit\Graph\Client;
+
+use Graphit\Graph\Common\ConnectionInterface;
+
+/**
+ * Die RemoteConnection ist die Basis-Klasse für ConnectionInterface
+ * Implementationen, die mit einem entfernten Graphen kommunizieren. All Aufrufe
+ * auf Methoden des ConnectionInterfaces werden an die abstrakte Methode
+ * RemoteConnection::call() weitergeleitet.
+ *
+ * @author Sebastian Wassen <sebastian.wassen@graph-it.org>
+ */
+abstract class RemoteConnection implements ConnectionInterface
+{
+  /**
+   * Leitet den Methodenaufruf an den Graphen weiter.
+   *
+   * @param string $method die aufgerufene Methode
+   * @param array  $params die Paremeter der Methode
+   *
+   * @return mixed|null die Antwort vom Graphen
+   */
+  protected abstract function call($method, array $params);
+
+  /** */
+  public function erzeuge($knoten_typ)
+  {
+    return $this->call('erzeuge', array($knoten_typ));
+  }
+
+  /** */
+  public function vernichte($node_guid)
+  {
+    return $this->call('vernichte', array($node_guid));
+  }
+
+  /** */
+  public function knotentyp($node_guid)
+  {
+    return $this->call('knotentyp', array($node_guid));
+  }
+
+  /** */
+  public function setze($node_guid, $attributknoten_typ, $wert)
+  {
+    return $this->call('setze', array($node_guid, $attributknoten_typ, $wert));
+  }
+
+  /** */
+  public function attribut($node_guid, $attributknoten_typ)
+  {
+    return $this->call('attribut', array($node_guid, $attributknoten_typ));
+  }
+
+  /** */
+  public function attribute($node_guid, $knoten_typ, $attribute)
+  {
+    return $this->call('attribute', array($node_guid, $knoten_typ, $attribute));
+  }
+
+  /** */
+  public function attributsknoten($attributknoten_typ, $wert)
+  {
+    return $this->call('attributsknoten', array($attributknoten_typ, $wert));
+  }
+
+  /** */
+  public function berechne($node_guid, $datenfunktion_name)
+  {
+    return $this->call('berechne', array($node_guid, $datenfunktion_name));
+  }
+
+  /** */
+  public function verknuepfe($node_guid1, $node_guid2)
+  {
+    return $this->call('verknuepfe', array($node_guid1, $node_guid2));
+  }
+
+  /** */
+  public function entknuepfe($node_guid1, $node_guid2)
+  {
+    return $this->call('entknuepfe', array($node_guid1, $node_guid2));
+  }
+
+  /** */
+  public function knoten($node_guid, $knoten_typ)
+  {
+    return $this->call('knoten', array($node_guid, $knoten_typ));
+  }
+
+  /** */
+  public function liste($node_guid, $knoten_typ, $reihenfolge = false, $eingrenzung = false, array $parameter = array())
+  {
+    return $this->call('liste', array($node_guid, $knoten_typ, $reihenfolge, $eingrenzung, $parameter));
+  }
+
+  /** */
+  public function listeattribute($node_guid, $knoten_typ, $attribute, $reihenfolge = false, $eingrenzung = false, array $parameter = array())
+  {
+    return $this->call('listeattribute', array($node_guid, $knoten_typ, $attribute, $reihenfolge, $eingrenzung, $parameter));
+  }
+
+  /** */
+  public function listezahl($node_guid, $knoten_typ, $eingrenzung = false, array $parameter = array())
+  {
+    return $this->call('listezahl', array($node_guid, $knoten_typ, $eingrenzung, $parameter));
+  }
+
+  /** */
+  public function moegliche($node_guid, $knoten_typ, $reihenfolge = false, $eingrenzung = false, array $parameter = array())
+  {
+    return $this->call('moegliche', array($node_guid, $knoten_typ, $reihenfolge, $eingrenzung, $parameter));
+  }
+
+  /** */
+  public function moeglicheattribute($node_guid, $knoten_typ, $attribute, $reihenfolge = false, $eingrenzung = false, array $parameter = array())
+  {
+    return $this->call('moeglicheattribute', array($node_guid, $knoten_typ, $attribute, $reihenfolge, $eingrenzung, $parameter));
+  }
+
+  /** */
+  public function moeglichezahl($node_guid, $knoten_typ, $eingrenzung = false, array $parameter = array())
+  {
+    return $this->call('moeglichezahl', array($node_guid, $knoten_typ, $eingrenzung, $parameter));
+  }
+
+  /** */
+  public function alle($knoten_typ, $reihenfolge = false, $eingrenzung = false, array $parameter = array())
+  {
+    return $this->call('alle', array($knoten_typ, $reihenfolge, $eingrenzung, $parameter));
+  }
+
+  /** */
+  public function alleattribute($knoten_typ, $attribute, $reihenfolge = false, $eingrenzung = false, array $parameter = array())
+  {
+    return $this->call('alleattribute', array($knoten_typ, $attribute, $reihenfolge, $eingrenzung, $parameter));
+  }
+
+  /** */
+  public function allezahl($knoten_typ, $eingrenzung = false, array $parameter = array())
+  {
+    return $this->call('allezahl', array($knoten_typ, $eingrenzung, $parameter));
+  }
+
+  /** */
+  public function aktion($node_guid, $aktionfunktion_name)
+  {
+    return $this->call('aktion', array($node_guid, $aktionfunktion_name));
+  }
+
+  /** */
+  public function erzeugeknoten($knoten_typ)
+  {
+    return $this->call('erzeugeknoten', array($knoten_typ));
+  }
+
+  /** */
+  public function vernichteknoten($node_guid)
+  {
+    return $this->call('vernichteknoten', array($node_guid));
+  }
+
+  /** */
+  public function service($graphmodulservice_name, ...$parameter)
+  {
+    return $this->call('service', func_get_args());
+  }
+
+  /** */
+  public function sessionattribut($schluessel, $default = null)
+  {
+    return $this->call('sessionattribut',[$schluessel, $default]);
+  }
+
+  /** */
+  public function sessionsetze($schluessel, $wert)
+  {
+    return $this->call('sessionsetze', [$schluessel, $wert]);
+  }
+  /* */
+}