Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to traverse a graph with ArangoDB and PHP

ArangoDB provides documents and edges as a low level way to produce graphs.

Let's say we have set up a graph with some vertices and edges. The graph represents the relationship between the vertices.

v2 is a child of v1
v3 is a child of v2
v4 is a child of v3
v5 is a child of v1
v6 is a child of v5

We want to be able to query:

  1. The path from v4 to v1
  2. All descendants of v1
  3. The children of v1
  4. All ancestors of v4
  5. The parents of v4

How to do that in PHP?

like image 215
thesilentman Avatar asked Mar 01 '13 12:03

thesilentman


1 Answers

The way to do this is by querying ArangoDB with its AQL (ArangoDB Query language).

There are a few commands for working with graphs in AQL. With PHP, we have to create a statement with the query and execute that.

The attached code uses the TRAVERSAL function in order to provide the results to the queries mentioned in the question.

This script sets up a document and an edge collection, fills it with vertices and connecting edges, and finally does the querying in order to provide the results.

It can be executed as is and will print out all the results.

<?php

namespace triagens\ArangoDb;


    // use this and change it to the path to autoload.php of the arangodb-php client if you're using the client standalone...
    // require __DIR__ . '/../vendor/triagens/ArangoDb/autoload.php';

// ...or use this and change it to the path to autoload.php in the vendor directory if you're using Composer/Packagist
require __DIR__ . '/../vendor/autoload.php';

// This function will provide us with our pre-configured connection options.
function getConnectionOptions()
{
    $traceFunc = function ($type, $data) {
        print "TRACE FOR " . $type . PHP_EOL;
    };

    return array(
        ConnectionOptions::OPTION_ENDPOINT      => 'tcp://localhost:8529/',
        // endpoint to connect to
        ConnectionOptions::OPTION_CONNECTION    => 'Close',
        // can use either 'Close' (one-time connections) or 'Keep-Alive' (re-used connections)
        ConnectionOptions::OPTION_AUTH_TYPE     => 'Basic',
        // use basic authorization
        /*
        ConnectionOptions::OPTION_AUTH_USER       => '',                      // user for basic authorization
        ConnectionOptions::OPTION_AUTH_PASSWD     => '',                      // password for basic authorization
        ConnectionOptions::OPTION_PORT            => 8529,                    // port to connect to (deprecated, should use endpoint instead)
        ConnectionOptions::OPTION_HOST            => "localhost",             // host to connect to (deprecated, should use endpoint instead)
        */
        ConnectionOptions::OPTION_TIMEOUT       => 5,
        // timeout in seconds
        //ConnectionOptions::OPTION_TRACE           => $traceFunc,              // tracer function, can be used for debugging
        ConnectionOptions::OPTION_CREATE        => false,
        // do not create unknown collections automatically
        ConnectionOptions::OPTION_UPDATE_POLICY => UpdatePolicy::LAST,
        // last update wins
    );
}

// This function tries to create vertices and edges for the example
function setupVerticesAndEdges($connection, $vertexCollection, $edgeCollection)
{

echo "We are creating 6 vertices...<br> ";
    //create example documents for the vertices
    $nameV1     = 'v1';
    $documentV1 = Document::createFromArray(
        array('name' => $nameV1, '_key' => $nameV1)
    );
    $nameV2     = 'v2';
    $documentV2 = Document::createFromArray(
        array('name' => $nameV2, '_key' => $nameV2)
    );
    $nameV3     = 'v3';
    $documentV3 = Document::createFromArray(
        array('name' => $nameV3, '_key' => $nameV3)
    );
    $nameV4     = 'v4';
    $documentV4 = Document::createFromArray(
        array('name' => $nameV4, '_key' => $nameV4)
    );
    $nameV5     = 'v5';
    $documentV5 = Document::createFromArray(
        array('name' => $nameV5, '_key' => $nameV5)
    );
    $nameV6     = 'v6';
    $documentV6 = Document::createFromArray(
        array('name' => $nameV6, '_key' => $nameV6)
    );

    echo "We are creating 5 edges...<br> ";
    //create example documents for the edges
    $nameE1     = 'e1';
    $documentE1 = Edge::createFromArray(
        array('name' => $nameE1, 'label' => 'child_of')
    );
    $nameE2     = 'e2';
    $documentE2 = Edge::createFromArray(
        array('name' => $nameE2, 'label' => 'child_of')
    );
    $nameE3     = 'e3';
    $documentE3 = Edge::createFromArray(
        array('name' => $nameE3, 'label' => 'child_of')
    );
    $nameE4     = 'e4';
    $documentE4 = Edge::createFromArray(
        array('name' => $nameE4, 'label' => 'child_of')
    );
    $nameE5     = 'e5';
    $documentE5 = Edge::createFromArray(
        array('name' => $nameE5, 'label' => 'child_of')
    );


    // Get instances of the vertice- and edgehandlers
    $documentHandler = new DocumentHandler($connection);
    $edgeHandler     = new EdgeHandler($connection);

    // Save the vertices
    try {
        // query the given $collectionId by example using the previously declared $exampleDocument array
        $result['v'][] = $documentHandler->save($vertexCollection, $documentV1);
        $result['v'][] = $documentHandler->save($vertexCollection, $documentV2);
        $result['v'][] = $documentHandler->save($vertexCollection, $documentV3);
        $result['v'][] = $documentHandler->save($vertexCollection, $documentV4);
        $result['v'][] = $documentHandler->save($vertexCollection, $documentV5);
        $result['v'][] = $documentHandler->save($vertexCollection, $documentV6);
    } catch (Exception $e) {
            // any other error
            echo ('An error occured. Exception: ' . $e);
    }


    // Save the edges
    try {
        echo "$nameV2 is a child of $nameV1<br> ";
        $result['e'][] = $edgeHandler->saveEdge(
            $edgeCollection,
            $vertexCollection . '/' . $nameV2,
            $vertexCollection . '/' . $nameV1,
            $documentE1,
            $options = array()
        );
        echo "$nameV3 is a child of $nameV2<br> ";
        $result['e'][] = $edgeHandler->saveEdge(
            $edgeCollection,
            $vertexCollection . '/' . $nameV3,
            $vertexCollection . '/' . $nameV2,
            $documentE2,
            $options = array()
        );
        echo "$nameV4 is a child of $nameV3<br> ";
        $result['e'][] = $edgeHandler->saveEdge(
            $edgeCollection,
            $vertexCollection . '/' . $nameV4,
            $vertexCollection . '/' . $nameV3,
            $documentE3,
            $options = array()
        );
        echo "$nameV5 is a child of $nameV1<br> ";
        $result['e'][] = $edgeHandler->saveEdge(
            $edgeCollection,
            $vertexCollection . '/' . $nameV5,
            $vertexCollection . '/' . $nameV1,
            $documentE4,
            $options = array()
        );
        echo "$nameV6 is a child of $nameV5<br> ";
        $result['e'][] = $edgeHandler->saveEdge(
            $edgeCollection,
            $vertexCollection . '/' . $nameV6,
            $vertexCollection . '/' . $nameV5,
            $documentE5,
            $options = array()
        );
        echo "<font style='font-family: monospace;'>";
        echo "$nameV1<br> ";
        echo "+ $nameV2<br> ";
        echo "|  + $nameV3<br> ";
        echo "|  |  + $nameV4<br> ";
        echo "+ $nameV5<br> ";
        echo "+ $nameV5<br> ";
        echo "|  + $nameV6<br> ";
        echo "</font>";

        // return the result;
        return $result;
    } catch (Exception $e) {
            // any other error
            echo ('An error occured. Exception: ' . $e);
    }
}


// helper function that takes the connection and the query to execute.
function doAQLQuery($connection, $query)
{
    // query through AQL

    $statement = new \triagens\ArangoDb\Statement($connection, array(
                                                                    "query"     => '',
                                                                    "count"     => true,
                                                                    "batchSize" => 1000,
                                                                    "_sanitize" => true,
                                                               ));
    $statement->setQuery($query);
    $cursor = $statement->execute();

    $result = $cursor->getAll();

    return $result;
}


// AQL query example functions


// Function that gets all paths from vertex v4 to v1
function getPathFromV4ToV1($connection)
{
    $query = 'FOR p IN PATHS(vertices_20130301_01, edges_20130301_01, "outbound")
  FILTER p.source._id == "vertices_20130301_01/v4" && p.destination._id == "vertices_20130301_01/v1"
  RETURN p';

    $result = doAQLQuery($connection, $query);

    return $result;
}

// Function that gets the paths to all descendants of v1

function getPathToAllDescendantsOfV1($connection)
{
    $query = 'FOR p IN TRAVERSAL(vertices_20130301_01, edges_20130301_01, "vertices_20130301_01/v1", "inbound", {
  strategy: "depthfirst",
  minDepth:1,
  paths: true,
  followEdges: [ { label: "child_of" } ]

})
RETURN p
';

    $result = doAQLQuery($connection, $query);

    return $result;
}

// Function that gets the paths to all children of v1
function getPathToChildrenOfV1($connection)
{
    $query = 'FOR p IN TRAVERSAL(vertices_20130301_01, edges_20130301_01, "vertices_20130301_01/v1", "inbound", {
  strategy: "depthfirst",
  maxDepth: 1,
  minDepth:1,
  paths: true,
  followEdges: [ { label: "child_of" } ]

})
RETURN p
';

    $result = doAQLQuery($connection, $query);

    return $result;
}

// Function that gets the paths to all parents of v4
function getPathToParentsOfV4($connection)
{
    $query = 'FOR p IN TRAVERSAL(vertices_20130301_01, edges_20130301_01, "vertices_20130301_01/v4", "outbound", {
  strategy: "depthfirst",
  maxDepth: 1,
  minDepth:1,
  paths: true,
  followEdges: [ { label: "child_of" } ]

})
RETURN p
';

    $result = doAQLQuery($connection, $query);

    return $result;
}

// Function that gets the paths to all ancestor of v4
function getPathToAllAncestorsOfV4($connection)
{
    $query = 'FOR p IN TRAVERSAL(vertices_20130301_01, edges_20130301_01, "vertices_20130301_01/v4", "outbound", {
  strategy: "depthfirst",
  minDepth:1,
  paths: true,
  followEdges: [ { label: "child_of" } ]

})
RETURN p
';

    $result = doAQLQuery($connection, $query);

    return $result;
}

// Function that drops collections given
function dropCollections($connection, $collections)
{
    // register a collection handler to work with the 'users' collection
    $collectionHandler = new CollectionHandler($connection);
    echo "dropping collections...";
    try {
        foreach ($collections as $collection) {
            $collectionHandler->drop($collection);
        }
        echo "dropped.<br>";
    } catch (Exception $e) {
        die ('Could not drop collection. Exception: ' . $e . '<br>');
    }
}

// *********************************************************************************************************************
// Start example code


// register the connection to ArangoDB
$connection = new Connection(getConnectionOptions());


// register a collection handler to work with the 'users' collection
$collectionHandler = new CollectionHandler($connection);


// assign the collection names...
$vertexCollection = 'vertices_20130301_01';
$edgeCollection   = 'edges_20130301_01';

// finally drop the collections...
// remark this line if you want to drop the collections by hand.
dropCollections($connection, array($vertexCollection, $edgeCollection));


// create the vertices and edges collections...
// remark those lines if you want to create the collection by hand.
echo "creating the '$vertexCollection' vertex collection...";
try {
    $collection = new Collection();
    $collection->setName($vertexCollection);
    $collectionHandler->create($collection);
    echo "created.<br>";
} catch (Exception $e) {
    echo ('Could not create collection. Exception: ' . $e . '<br>');
}


echo "creating the '$edgeCollection' edge collection...";
try {
    $collection = new Collection();
    $collection->setName($edgeCollection);
    $collection->setType(3);
    $collectionHandler->create($collection);
    echo "created.<br>";
} catch (Exception $e) {
    echo ('Could not create collection. Exception: ' . $e . '<br>');
}


// setup our vertices and edges....
echo "trying to setup our vertices and edges... <br>";
$result = setupVerticesAndEdges($connection, $vertexCollection, $edgeCollection);


// AQL Examples

// get the path from vertex v4 to v1
$result = getPathFromV4ToV1($connection);
echo "<br>*****************************************<br>";
echo "get all paths from vertex v4 to v1<br>";
echo "<br>*****************************************<br>";
var_dump($result);


// get the paths to all descendants of v1
$result = getPathToAllDescendantsOfV1($connection);
echo "<br>*****************************************<br>";
echo "get the paths to all descendants of v1<br>";
echo "<br>*****************************************<br>";
var_dump($result);

//get the paths to all children of v1
$result = getPathToChildrenOfV1($connection);
echo "<br>*****************************************<br>";
echo "get the paths to all children of v1<br>";
echo "<br>*****************************************<br>";
var_dump($result);


// get the paths to all ancestors of v4
$result = getPathToAllAncestorsOfV4($connection);
echo "<br>*****************************************<br>";
echo "get the paths to all ancestors of v4<br>";
echo "<br>*****************************************<br>";
var_dump($result);

//get all paths to all parents of v4
$result = getPathToParentsOfV4($connection);
echo "<br>*****************************************<br>";
echo "get all paths to all parents of v4<br>";
echo "<br>*****************************************<br>";
var_dump($result);
like image 90
thesilentman Avatar answered Oct 05 '22 22:10

thesilentman