Usando Neo4j y PHP

Neo4j nos ofrece gran cantidad de clientes para interactuar con él mediante diferentes lenguajes de programación (Drivers Neo4j).

En nuestro ejemplo interactuaremos con Neo4j mediante PHP. Para PHP existen varios drivers (Neo4jPHP, NeoClient, …). En la web de Neo4j puedes encontrar una lista más completa de clientes PHP para Neo4j. En este tutorial usaremos NeoClient.

Instalación

Como paso previo a la instalación de NeoClient, necesitas tener instalado Composer, un gestor de dependencias para PHP. En el Tutorial sobre configuración de un entorno LAMP+NoSQL están disponibles los pasos para tener instalado todo lo necesario para usar Neo4j con PHP (Neo4j, Servidor Apache, PHP y Composer).

Siguiendo la Información de instalación de NeoClient hay que crear en la carpeta del proyecto un archivo composer.json en el que se especifica la dependencia con NeoClient.

A continuación, en la carpeta del proyecto instalaremos las dependencias con el comando siguiente

composer install

Conexión a Neo4j

Para usar las clases de NeoClient debemos hacer referencia el espacio de nombres de NeoClient (Neoxygen\NeoClient). Además, antes de proceder a crear la conexión tenemos que importar el archivo autoload.php generado por Composer al instalar las dependencias.

La conexión la crearemos mediante el método create() de la clase ClientBuilder de NeoClient. En ella indicaremos varios parámetros, destacando el host y el puerto. En nuestro caso hemos usado localhost y 7474 (el puerto predeterminado de Neo4j).

Este código lo podemos guardar en un archivo (p.e. connection.php) para que pueda ser utilizado por cualquier script de nuestra aplicación que tenga que interactuar con Neo4j.

Interacción con Neo4j

Básicamente, la interacción con Neo4j la realizaremos mediante el envío de consultas Cypher a través de la conexión que hemos establecido en el paso anterior.

El envío de estas consultas se realiza aplicando a una conexión el método sendCypherQuery($query, [$params]). Este método toma como argumento una consulta Cypher y opcionalmente un array asociativo de parámetros.

Creación de nodos

Los nodos son creados definiendo expresiones en Cypher que serán pasadas como argumento al método sendCypherQuery() sobre la conexión creada.

A continuación vemos cómo crear un nodo Person para un actor con nombre (name) John Doe, y un nodo Movie para una película con título (title) GGVD.

Creación de relaciones

Al igual que los nodos, las relaciones son creadas definiendo expresiones en Cypher que serán pasadas como argumento al método sendCypherQuery() sobre la conexión creada.

A continuación vemos cómo crear una relación entre los nodos Person y Movie creados anteriormente. La relación se denomina ACTED_IN y tiene asociada la propiedad roles con el valor ['Johnnie'] indicando que el actor John Doe tiene el papel (rol) de Johnnie en la película GGVD.

Uso de parámetros en las consultas

Las consultas Cypher que pasamos al método sendCypherQuery() se pueden definir parametrizadas. Los parámetros se asignarán posteriormente antes de enviar la consulta a Neo4j.

A continuación se muestra la misma consulta que usamos anteriormente para definir una relación ACTED_IN entre un nodo Person y un nodo Movie, pero ahora la crearemos de forma parametrizada. En este caso, el nombre de la persona es pasado como parámetro ({theName}), así como el título de la película ({theTitle}).

Para asignar valores a los parámetros crearemos un array asociativo que tendrá tantos elementos como parámetros hayamos definido. Cada elemento del array de parámetros tendrá como clave el parámetro, y como valor el valor del parámetro.

En el ejemplo, el array $params define los valores de los dos parámetros definidos en la consulta Cypher.

Por último, para ejecutar la consulta con el método sendCypherQuery() pasaremos la cadena de la consulta seguida del array de parámetros.

Actualización y eliminación

Como hemos comentado anteriormente, la interacción con Neo4j usando NeoClient se realiza enviando consultas Cypher al método sendCypherQuery(), independientemente de si son para crear nodos, para crear relaciones, o si son para actualizar o eliminar datos de la base de datos.

Por tanto, para actualizar o eliminar los ejemplos que añadimos anteriomente, basta con definir la expresión Cypher adecuada y ejecutarla con el método sendCypherQuery().

El código siguiente elimina el path creado en los ejemplos anteriores formado por el actor John Doe, la película GGVD, y el papel de dicho actor en dicha película.

Consulta básica

Los resultados que devuelve una consulta realizada con el método sendCypherQuery() lo asignaremos a un objeto que posteriormente procesaremos para mostrar el resultado de la consulta.

Básicamente se trata de hacer lo siguiente:

El ejemplo siguiente muestra los títulos de las películas de 1998.

Consultas que devuelven un solo nodo

En ocasiones nuestras consultas sólo devuelven un nodo, o bien sólo estamos interesados en recuperar un solo nodo de un resultset.

El método getSingleNode() aplicado a un resultset devuelve un nodo del resultset. Si el resultset contenía un solo nodo, getSingleNode() nos lo devuelve.

El ejemplo siguiente muestra el año de producción de la película When Harry Met Sally. La consulta recupera un nodo de la base de datos, y mediante getSingleNode() accedemos a él. Una vez recuperado ya le podemos aplicar los métodos aplicables a nodos, como getProperty(<property>).

¿Y si RETURN devuelve varios elementos?

Es habitual que las expresiones Cypher incluyan en la cláusula RETURN varios elementos (p.e. varios tipos de nodos, relaciones, y demás).

Imaginemos la siguiente expresión Cypher:

MATCH (m: Movie {released: 1998})<-[r:ACTED_IN]-(p: Person) RETURN p, r, m

Para este caso, los métodos getNodes() y getSingleNode() no funcionarían correctamente al aplicarlos sobre el resultset, ya que no sabrían si actuar sobre los nodos p, las relaciones r, o los nodos m.

En casos como este, aplicaremos el método get(<elemento>) sobre el resultset para obtener el elemento del RETURN en el que estemos interesados.

El siguiente script ejecuta una consulta que devuelve los nodos película, persona y relación ACTED_IN para películas de 1998. A continuación, asigna los tres elementos del RETURN a tres variables. Para acceder a los elementos que devuelve el RETURN se usa el método get(<elemento>) sobre el resultset

Recorrido del conjunto de nodos

En el script anterior, $actors contiene la lista de nodos Person de las películas recuperadas. Para acceder a ellos, basta con iterar sobre $actors con un foreach y acceder a las propiedades de cada nodo con getProperty(<propiedad>).

Recorrido del conjunto de relaciones

En el script anterior, $relationships contiene la lista de relaciones entre los nodos de las películas recuperadas y los nodos de los actores que actuan en ellas.

Cada relación tiene dos extremos, el nodo de inicio y el nodo de fin de la relación. Los métodos getStartNode() y getEndNode() aplicados a una relación devuelven, respectivamente, el nodo de inicio y el nodo de fin de la relación.

Para acceder a los nodos de las relaciones tendremos que iterar sobre $relationships con un foreach, y acceder a los nodos de inicio y fin con getStartNode() y getEndNode(), respectivamente. A continuación, y ya sobre cada nodo podremos acceder a las propiedades de cada nodo con getProperty(<propiedad>).

Obtener las relaciones de un nodo

Si la consulta que hemos realizado, además de los nodos, contiene los paths o relaciones con los nodos, podemos estar interesados en mostrar las relaciones que llegan o salen de un nodo.

Los métodos getInboundRelationships() y getOutboundRelationships() devuelven, respectivamente, las relaciones que llegan o salen de un nodo. El método getRelationships() devuelve todas las relaciones que mantiene un nodo, ya sean de entrada o de salida.

El script siguiente muestra un ejemplo en el que a partir de un conjunto de películas ($movies), realiza una iteración con un foreach y para cada película muestra su título, obtiene todos las relaciones de entrada con getInboundRelationships(), y finalmente para cada relación obtiene su nodo de origen (el actor) con getStartNode(), y muestra su nombre con getProperty('name').

Otro ejemplo interesante lo podemos encontrar en el fragmento siguiente. A partir de un conjunto de películas ($movies) se realiza una iteración con un foreach y para cada película se muestra su título, y se obtienen todas las relaciones con getRelationships(<relacion>).

Para cada relación se obtiene su nodo de origen (que podrá ser, según la relación, actor, director, guionista o productor) con getStartNode(), y muestra su nombre con getProperty('name').

Puedes descargar el código de este tutorial del GitHub de este proyecto.