PHP
Article
By Tobias Nyholm

Introducing the Neo4j Symfony Bundle

By Tobias Nyholm

This article was peer reviewed by Wern Ancheta and Christophe Willemsen. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!


Why Graphs?

There is no such thing as disconnected information, no matter where you look – people, events, places, things, documents, applications and the information about them is all heavily connected. As the volume of data grows, so does the number and dynamicity of its connections. And if you’ve tried in the past to store and query that highly connected, semi-structured data in any database, you probably experienced a lot of challenges.

Example graph

The Labeled Property Graph in a native Graph Database

Neo4j was built to handle exactly this real-world information without compromising on the number and types of connections you can have relating your entities. It is an open-source NoSQL database that uses the Labeled Property Graph to store the entities of your domain model (diagram) as Nodes and their connections as Relationships each of which can have arbitrary properties.

Nodes and releationships with properties

Neo4j is not just a graph layer on top of another database but a full blown, transactional (ACID) database with everything from managing pages of records on disk to providing a scalable, secure cluster. And as a native graph database it uses dedicated data structures to store and query highly connected data efficiently. Unlike in other databases, (complex) JOIN queries are not computed repeatedly at query time. Instead, relationships between entities are stored directly. During querying the database engine can use direct record-pointers for constant time lookups.

The open Cypher Query Language

This doesn’t just extend to modeling or storage, even the Cypher graph query language that comes with Neo4j is focused around graph patterns, this time represented as ASCII-art in your query: (dan:Person {name:"Dan"})-[:LOVES]->(ann:Person {name:"Ann"}), which make your queries extremely readable even for non-developers, e.g. here is a recommendation query (“customers like you also bought this”):

MATCH (c:Customer)-[:BOUGHT]->(:Product)<-[:BOUGHT]-(o:Customer)-[:BOUGHT]->(reco:Product)
WHERE c.id = 123 AND NOT (c)-[:BOUGHT]->(reco)
RETURN reco.name, count(*) as frequency
ORDER BY frequency DESC LIMIT 10;

Symfony, a rapid development framework for PHP

Symfony is the role model of frameworks for modern PHP. The framework has a component approach and has been around for the last 11 years. From the Symfony ecosystem we’ve seen projects like Composer, Twig, Swiftmailer, Assetic, Monolog and many more. Thanks to the component approach, it has been made easy for other projects and frameworks to reuse code from Symfony. Projects like Laravel, Silex, Sylius, Drupal, phpBB, eZ are all using Symfony components.

A key factor of Symfony’s success is the flexibility of the framework in combination with the ease of use. The standard edition of Symfony comes with Doctrine as default database layer abstraction which supports some major databases like MySQL and MongoDB. But neither the database layer nor Doctrine is a primary citizen in Symfony. They could easily be replaced by something else.

--ADVERTISEMENT--

Introducing Symfony Neo4j Bundle

To provide a smooth integration between Neo4j and Symfony we’ve created the SymfonyNeo4jBundle. It wraps the excellent PHP community client by Graphaware and creates a solid Symfony experience. Thanks to the WebProfiler integration, you will see all your database calls, all the queries and their results. You will even have a view over any exceptions that are thrown when interacting with the database. You will also get detailed statistics about each database call. This makes debugging your application way easier.

The bundle also integrates the client events with the Symfony event dispatcher. You could now create event subscribers that listen to the interactions with Neo4j e.g. for integration with MonologBundle to log all your database queries.

The bundle is not opinionated in how you are using Neo4j. Using the OGM is optional. Advanced Neo4j users will have full control over the client and what Cypher gets executed.

An API like Doctrine

For developers who are familiar with Doctrine, they will know how to use GraphAware’s OGM (Object Graph Mapper). The OGM has an EntityManager that implements Doctrine’s ObjectManager interface, which gives you functions like “find”, “remove”, “persist” and “flush”. Developers will have the exact same experience working with models from Neo4j’s OGM compared to Doctrine’s MySQL EntityManagers.

Configuration

Like in most modern frameworks, Symfony separates configuration from the code. This is good software practice which the Neo4jBundle adheres to. It provides the ability to easily configure multiple connections, multiple clients and multiple entity managers. For each connection you can decide if you want to use HTTP or the new binary “bolt” protocol.

Thanks to Symfony’s configuration component, you may use Yaml, XML or PHP to specify your configuration. The default configuration will set up one connection to 127.0.0.1:7474 with the defaults username / password.

Getting started with the Symfony-Neo4j-Bundle

After installing the the bundle you may start using the clients

$client = $this->get('neo4j.client');
$results = $client->run('MATCH (n:Movie) RETURN n LIMIT 5');
foreach ($results->records() as $record) {
   $node = $record->get('n');
   echo $node->get('title'); // "The Matrix"
}

If you are used to Doctrine you may want to use the OGM. You need to add annotations to your models so the OGM will understand and map the properties properly.

use GraphAware\Neo4j\OGM\Annotations as OGM;

/**
 * @OGM\Node(label="User")
 */
class User
{
    /** @OGM\GraphId() */
    protected $id;

    /** @OGM\Property(type="string") */
    protected $name;

    /** @OGM\Property(type="int") */
    protected $age;

    // Getters and setters
}
$em = $this->get('neo4j.entity_manager');

$bart = new User('Bart Johnson', 33);
$em->persist($bart);
$em->flush();

// Retrieve from database
$john = $em->getRepository(User::class)->findOneBy('name', 'John Doe');
echo $john->getAge();

Here is an example of what the profiler may look like:

Example of the Symfony profiler page

Relationship and relationship entities

The major difference from Doctrine and MySQL is that relationships are first class citizens in Neo4j. They are as important as the nodes themselves. To create a simple relationship between a person and a movie you would use the @OGM\Relationship annotation.

use GraphAware\Neo4j\OGM\Annotations as OGM;
use GraphAware\Neo4j\OGM\Common\Collection;

/**
*
* @OGM\Node(label="Person")
*/
class Person
{
   /**
    * @var int
    *
    * @OGM\GraphId()
    */
   protected $id;

   // other code

   /**
    * @var Movie[]|Collection
    *
    * @OGM\Relationship(type="ACTED_IN", direction="OUTGOING", collection=true, mappedBy="actors", targetEntity="Movie")
    */
   protected $movies;

   public function __construct()
   {
       $this->movies = new Collection();
   }

   // other code

   /**
    * @return Movie[]|Collection
    */
   public function getMovies()
   {
       return $this->movies;
   }
}

The Movie class has an INCOMING relationship from Person.

use GraphAware\Neo4j\OGM\Annotations as OGM;
use GraphAware\Neo4j\OGM\Common\Collection;

/**
*
* @OGM\Node(label="Movie")
*/
class Movie
{
   /**
    * @var int
    *
    * @OGM\GraphId()
    */
   protected $id;

   // other code

   /**
    * @var Person[]|Collection
    *
    * @OGM\Relationship(type="ACTED_IN", direction="INCOMING", collection=true, mappedBy="movies", targetEntity="Person")
    */
   protected $actors;

   public function __construct()
   {
       $this->actors = new Collection();
   }

   // other code

   /**
    * @return Person[]|Collection
    */
   public function getActors()
   {
       return $this->actors;
   }
}

The relationship itself can have also properties. Example if a User rates a Movie then the relationship would probably have a score. This can be achieved with relationship entities. Read more about them at the OGM’s documentation.

Example project

There is an example project that you may use to test the bundle. Use the the steps below to install the project:

git clone git@github.com:neo4j-examples/movies-symfony-php-bolt.git
composer install

Import data fixture (https://neo4j.com/developer/example-project/#_data_setup)

php bin/console server:run

Go to http://127.0.0.1:8000/

There is more information about the bundle in the repository on Github. Please give us comments and feedback of what we did right and how we can improve. Feel free to raise issues or contribute to the project.

Resources:

Recommended
Sponsors
The most important and interesting stories in tech. Straight to your inbox, daily. Get Versioning.
Login or Create Account to Comment
Login Create Account