Search code examples
phpneo4jcypherneo4jphp

How can I translate this Neo4j cypher query to a PHP cypher DSL?


We are translating a Neo4j cypher query to PHP cypher DSL (Domain-Specific Language) using the php-cypher-dsl library to have more flexibility.

We struggle with this because the library's documentation is not really explaining how to use it in much detail. Given that we have a plain query in cypher Neo4j:

MATCH (a:Resource), (b:Resource) 
WHERE a.uri = $uri AND b.uri = $object
CREATE (a)-[r:{$propertyUri}]->(b)
RETURN type(r)

How can we translate this Neo4j cypher query to PHP cypher DSL?


Solution

  • The first thing you need to do is create all the nodes that you want to match on, as well as the parameters you want to use, like so:

    $a = node("Resource"); // (a:Resource)
    $b = node("Resource"); // (b:Resource)
    
    $uri = parameter("uri"); // $uri
    $object = parameter("object"); // $object
    

    The node function takes as its only argument the label of the node and the parameter function the name of the parameter. Next, you can start to compose your query. You can start a new query using the query function, like so:

    $query = query();
    

    This returns a Query object, which has a number of methods to add new clauses to your query, such as match, set, where and create. These correspond to the clauses that Cypher offers.

    We start by matching on the nodes $a and $b:

    $query = query() // Start a new query
        ->match([$a, $b]) // Match on both "a" and "b"
        ...
    

    Next, we want to create the condition to match on. php-cypher-dsl comes with a builder pattern for creating such expressions:

        ...
        ->where($a->property("uri")->equals($uri)->and($b->property("uri")->equals($object))
        ...
    

    Next, before we create our CREATE clause, we need to create a new variable $r that gets shared between our CREATE and RETURN clause:

    $r = variable(); // Create a new variable
    

    To create the CREATE clause, we need to perform some trickery to remove the :Resource label from our nodes again. This part of the API still needs some improvement. We can get a new node with the same variable by using node()->withVariable($a->getVariable()). All together, that looks like:

        ...
        ->create(node()->withVariable($a->getVariable())->relationshipTo(node()->withVariable($b->getVariable()), type: $propertyUri, name: $r))
        ...
    

    Finally, we can return the value we want:

        ...
        ->returning(Procedure::raw('type', $r)) // The "type" function is not implemented natively, so we use raw
        ->toQuery(); // Convert the query to a string
    

    All put together, the code looks like this:

    use WikibaseSolutions\CypherDSL\Expressions\Procedures\Procedure;
    use function WikibaseSolutions\CypherDSL\node;
    use function WikibaseSolutions\CypherDSL\parameter;
    use function WikibaseSolutions\CypherDSL\query;
    use function WikibaseSolutions\CypherDSL\variable;
    
    $propertyUri = "http://example.com";
    
    $a = node("Resource");
    $b = node("Resource");
    $uri = parameter("uri");
    $object = parameter("object");
    $r = variable();
    
    $query = query()
        ->match([$a, $b])
        ->where($a->property("uri")->equals($uri)->and($b->property("uri")->equals($object)))
        ->create(node()->withVariable($a->getVariable())->relationshipTo(node()->withVariable($b->getVariable()), type: $propertyUri, name: $r))
        ->returning(Procedure::raw('type', $r))
        ->toQuery();
    

    Which I think is much less readable than using a normal query with variables. If you don't require complex logic to build queries (and only use pre-created queries, such as the one in your question), I'd recommend you to not use this library.