Neo4j is an open source, high performance graph database written in Java. It is a type of NoSQL database but much different from MongoDB. Graph databases are made of nodes and relationships, which both have properties. Nodes can represent something like a user and properties of the user may represent name, birthday, and so on. Some examples of relationships are 'friend', 'watch', 'read', 'recommend', and so forth. The primary advantage of using graph databases over document databases is its extraordinary ability to do complex operations very fast when it comes to related objects. Such cases include, but are not limited to, retrieving distinct friends of friends in a social network model who share similar interests or retrieving most recommended restaurants within a specific radius based on different weights from preferences. For further explanation on graph databases and Neo4j, please visit Neo4j Documentation.

Currently there are no fully functional OGM (similar to Doctrine's ORM/ODM) for PHP. But using PHP wrapper for Neo4j REST interface, Neo4jphp, is more than sufficient to build an awesome application. Follow the instructions on the wiki and install the package by using composer. Next, make the Client available as a service.

services.yml:

# services.yml
parameters:
    neo4j_client.class: Everyman\Neo4j\Client

services:
    neo4j.client:
        class: "%neo4j_client.class%"

Create a directory inside your bundle called Graph. Files inside this directory will function similar to that of a repository in ORM or ODM. Let's create a class called BaseGraph that all graphs will have to extend.

BaseGraph.php:

<?php
namespace Melt\AwesomeBundle\Graph;

use Everyman\Neo4j\Client;

abstract class BaseGraph
{
    protected $client;

    public function __construct(Client $client)
    {
        $this->client = $client;
    }
}

Note that all classes that extend BaseGraph will have to be defined as a service and by doing so, we can inject Neo4j\Client as a dependency. Even though we do not have Doctrine's mapping abilities at our disposal, we can still model and layout the structure for entities. Let's model a simple user.

User.php:

<?php
namespace Melt\AwesomeBundle\Model;

class User
{
    protected $id;

    protected $username;

    protected $email;

    protected $name;

    // ... Define getter and setter methods
}

Now, we need some way to convert this model into data that the Neo4j client can understand. Create a directory inside your bundle called GraphTransformer. All the files inside this directory will be responsible for transforming model data into graph properties and vice versa.

UserGraphTransformer.php:

<?php
namespace Melt\AwesomeBundle\GraphTransformer;

use Melt\UserBundle\Model\User;
use Melt\UserBundle\Graph\UserGraph;
use Everyman\Neo4j\Node;

class UserGraphTransformer
{
    public function graphToModel(Node $userNode)
    {
        $user = new User();
        $nodeProperties = $userNode->getProperties();
        $user->setEmail($userProperties['email']);
        $user->setUsername($userProperties['username']);
        $user->setName($userProperties['name']);

        return $user;
    }

    public function modelToGraphProperties(User $user)
    {
        $properties = array(
            'id'       => $user->getId(),
            'username' => $user->getUsername(),
            'email'    => $user->getEmail(),
            'name'     => $user->getName(),
        );

        return $properties;
} }

Using GraphTransformer seems hack-ish to me, but until a fully functional OGM comes out, this will have to do. Now all we have left is to create a UserGraph. I will prepopulate it with some methods that may be commonly used. Also, let's not forget to add UserGraph to our services.

services.yml:

# services.yml
services: 
    # ...

    melt_awesome.user_graph:
        class: Melt\AwesomeBundle\Graph\UserGraph
        arguments: [@neo4j.client]

UserGraph.php:

<?php
namespace Melt\AwesomeBundle\Graph;

use Everyman\Neo4j\Cypher\Query;
use Everyman\Neo4j\Index\NodeIndex;

class UserGraph
{
    /**
     * @return \Everyman\Neo4j\Node
     */
    public function getUserbyUsername($username)
    {
        $queryString = "START n=node:user(username = {username}) ".
                       "RETURN n ".
                       "LIMIT 1";
        $query = new Query($this->client, $queryString, array(
            'username' => $username,
        ));
        $result = $query->getResultSet();
        
        if (count($result) == 0)
            return null;

        $row = $result->current();
        return $row['n'];
    }

    public function createUser($userProperties)
    {
        $userNode = $this->client->makeNode();
        $userNode->setProperties($userProperties)->save();
        $userIndex = new NodeIndex($this->client, 'user');
        $userIndex->add($userNode, 'username', $userNode->getProperty('username'));
        $userIndex->add($userNode, 'email', $userNode->getProperty('email'));
        $userIndex->save();
    }
}

In our UserGraph class, we created a method to fetch the user node from a username as well as a method to create a user defined by UserGraphTransformer. The createUser method also creates indexes on user email and username for each user. In order to use our code that we wrote, let's head to the controller.

<?php
namespace Melt\AwesomeBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Melt\AwesomeBundle\Model\User;
use Melt\AwesomeBundle\GraphTransformer\UserGraphTransformer;

class AwesomeController extends Controller
{
    // ...
    // Initialize UserGraph as member variable

    // Example
    public function createUserAction(Request $request)
    {
        $user = new User();
        $user->setUsername($request->get('username'));
        $user->setEmail($request->get('email'));
        $user->setName($request->get('name'));
        $userGraphTransformer = new UserGraphTransformer();
        $userGraphProperties = $userGraphTransformer->modelToGraphProperties($user);
        $this->userGraph->creatUser($userGraphProperties);

        // Return some response
    }
}

This was a very basic implementation of Neo4j. Nowhere near perfect, but hopefully this simple Neo4j integration can point those interested in using Neo4j with Symfony2 toward the right direction.