From b847f512f7916cea2345ac088ff219806a91def7 Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Fri, 30 Jan 2015 10:08:56 +0200 Subject: [PATCH 1/9] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f5d0b66..35a8a118 100644 --- a/README.md +++ b/README.md @@ -650,7 +650,7 @@ $post = $edge->related(); #### HyperEdge This edge comes as a result of a [Polymorphic Relation](#polymorphic) representing an edge involving -tow other edges **left** and **right** that can be accessed through the `left()` and `right()` methods. +two other edges **left** and **right** that can be accessed through the `left()` and `right()` methods. This edge is treated a bit different than the others since it is not a direct relationship between two models which means it has no specific direction. From 65aa0d5114dd5b997ac1fa9765b61c9658312bdf Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Fri, 30 Jan 2015 10:30:08 +0200 Subject: [PATCH 2/9] Extra example which I don't think belongs here --- README.html | 1007 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 8 - README.md.html | 977 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1984 insertions(+), 8 deletions(-) create mode 100644 README.html create mode 100644 README.md.html diff --git a/README.html b/README.html new file mode 100644 index 00000000..cff223a7 --- /dev/null +++ b/README.html @@ -0,0 +1,1007 @@ +

SensioLabsInsight

+ +

Build Status

+ +

NeoEloquent

+ +

Neo4j Graph Eloquent Driver for Laravel 4

+ +

Quick Reference

+ + + +

Installation

+ +

Add the package to your composer.json and run composer update.

+ +

json +{ + "require": { + "vinelab/neoeloquent": "*" + } +} +

+ +

Add the service provider in app/config/app.php:

+ +

php +'Vinelab\NeoEloquent\NeoEloquentServiceProvider', +

+ +

The service provider will register all the required classes for this package and will also alias +the Model class to NeoEloquent so you can simply extend NeoEloquent in your models.

+ +

Configuration

+ +

in app/config/database.php or in case of an environment-based configuration app/config/[env]/database.php +make neo4j your default connection:

+ +

php +'default' => 'neo4j', +

+ +

Add the connection defaults:

+ +

php +'connections' => [ + 'neo4j' => [ + 'driver' => 'neo4j', + 'host' => 'localhost', + 'port' => '7474', + 'username' => null, + 'password' => null + ] +] +

+ +

Documentation

+ +

Models

+ + + +

php +class User extends NeoEloquent {} +

+ +

As simple as it is, NeoEloquent will generate the default node label from the class name, +in this case it will be :User. Read about node labels here

+ +

Namespaced Models

+ +

When you use namespaces with your models the label will consider the full namespace.

+ +

```php +namespace Vinelab\Cms;

+ +

class Admin extends NeoEloquent { } +```

+ +

The generated label from that relationship will be VinelabCmsAdmin, this is necessary to make sure +that labels do not clash in cases where we introduce another Admin instance like +Vinelab\Blog\Admin then things gets messy with :Admin in the database.

+ +

Custom Node Labels

+ +

You may specify the label(s) you wish to be used instead of the default generated, they are also +case sensitive so they will be stored as put here.

+ +

```php +class User extends NeoEloquent {

+ +
protected $label = 'User'; // or array('User', 'Fan')
+
+protected $fillable = ['name', 'email'];
+
+ +

}

+ +

$user = User::create(['name' => 'Some Name', 'email' => 'some@email.com']); +```

+ +

NeoEloquent has a fallback support for the $table variable that will be used if found and there was no $label defined on the model.

+ +

```php +class User extends NeoEloquent {

+ +
protected $table = 'User';
+
+ +

} +```

+ +

Do not worry about the labels formatting, You may specify them as array('Label1', 'Label2') or separate them by a column : and prepending them with a : is optional.

+ +

Soft Deleting

+ +

To enable soft deleting you'll need to use Vinelab\NeoEloquent\Eloquent\SoftDeletingTrait +instead of Illuminate\Database\Eloquent\SoftDeletingTrait and just like Eloquent you'll need the $dates in your models as follows:

+ +

```php +use Vinelab\NeoEloquent\Eloquent\SoftDeletingTrait;

+ +

class User extends NeoEloquent {

+ +
use SoftDeletingTrait;
+
+protected $dates = ['deleted_at'];
+
+ +

} +```

+ +

Relationships

+ + + +

Let's go through some examples of relationships between Nodes.

+ +

One-To-One

+ +

```php +class User extends NeoEloquent {

+ +
public function phone()
+{
+    return $this->hasOne('Phone');
+}
+
+ +

`` +This represents anOUTGOINGrelationship direction from the:Usernode to a:Phone`.

+ +
Saving
+ +

php +$phone = new Phone(['code' => 961, 'number' => '98765432']) +$relation = $user->phone()->save($phone); +

+ +

The Cypher performed by this statement will be as follows:

+ +

+MATCH (user:`User`) +WHERE id(user) = 1 +CREATE (user)-[:PHONE]->(phone:`Phone` {code: 961, number: '98765432', created_at: 7543788, updated_at: 7543788}) +RETURN phone; +

+ +
Defining The Inverse Of This Relation
+ +

```php +class Phone extends NeoEloquent {

+ +
public function user()
+{
+    return $this->belongsTo('User');
+}
+
+ +

} +```

+ +

This represents an INCOMING relationship direction from +the :User node to this :Phone node.

+ +
Associating Models
+ +

Due to the fact that we do not deal with foreign keys, in our case it is much +more than just setting the foreign key attribute on the parent model. In Neo4j (and Graph in general) a relationship is an entity itself that can also have attributes of its own, hence the introduction of +Edges

+ +
+

Note: Associated models does not persist relations automatically when calling associate().

+
+ +

```php +$account = Account::find(1986);

+ +

// $relation will be Vinelab\NeoEloquent\Eloquent\Edges\EdgeIn +$relation = $user->account()->associate($account);

+ +

// Save the relation +$relation->save(); +```

+ +

The Cypher performed by this statement will be as follows:

+ +

+MATCH (account:`Account`), (user:`User`) +WHERE id(account) = 1986 AND id(user) = 9862 +MERGE (account)<-[rel_user_account:ACCOUNT]-(user) +RETURN rel_user_account; +

+ +

The Cypher performed by this statement will be as follows:

+ +

+MATCH (phone:Phone) (phone)<-[:PHONE]-(user:User) +WHERE id(phone) = 1006 +RETURN user; +

+ +

One-To-Many

+ +

```php +class User extends NeoEloquent {

+ +
public function posts()
+{
+    return $this->hasMany('Post', 'POSTED');
+}
+
+ +

} +```

+ +

This represents an OUTGOING relationship direction +from the :User node to the :Post node.

+ +

php +$user = User::find(1); +$post = new Post(['title' => 'The Title', 'body' => 'Hot Body']); +$user->posts()->save($post); +

+ +

Similar to One-To-One relationships the returned value from a save() statement is an +Edge[In|Out]

+ +

The Cypher performed by this statement will be as follows:

+ +

+MATCH (user:`User`) +WHERE id(user) = 1 +CREATE (user)-[rel_user_post:POSTED]->(post:`Post` {title: 'The Title', body: 'Hot Body', created_at: '15-05-2014', updated_at: '15-05-2014'}) +RETURN rel_user_post; +

+ +
Defining The Inverse Of This Relation
+ +

```php +class Post extends NeoEloquent {

+ +
public function author()
+{
+    return $this->belongsTo('User', 'POSTED');
+}
+
+ +

} +```

+ +

This represents an INCOMING relationship direction from +the :User node to this :Post node.

+ +

Many-To-Many

+ +

```php +class User extends NeoEloquent {

+ +
public function followers()
+{
+    return $this->belongsToMany('User', 'FOLLOWS');
+}
+
+ +

} +```

+ +

This represents an OUTGOING relationship between a :User node and another :User.

+ +

php +$jd = User::find(1012); +$mc = User::find(1013); +

+ +

$jd follows $mc:

+ +

php +$jd->followers()->save($mc); +

+ +

Or using the attach() method:

+ +

php +$jd->followers()->attach($mc); +// Or.. +$jd->followers()->attach(1); // 1 being the id of $mc ($mc->getKey()) +

+ +

The Cypher performed by this statement will be as follows:

+ +

+MATCH (user:`User`), (followers:`User`) +WHERE id(user) = 1012 AND id(followers) = 1013 +CREATE (user)-[:FOLLOWS]->(followers) +RETURN rel_follows; +

+ +

$mc follows $jd back:

+ +

php +$mc->followers()->save($jd); +

+ +

The Cypher performed by this statement will be as follows:

+ +

+MATCH (user:`User`), (followers:`User`) +WHERE id(user) = 1013 AND id(followers) = 1012 +CREATE (user)-[rel_user_followers:FOLLOWS]->(followers) +RETURN rel_follows; +

+ +

get the followers of $jd

+ +

php +$followers = $jd->followers; +

+ +

The Cypher performed by this statement will be as follows:

+ +

+MATCH (user:`User`), (followers:`User`), (user)-[rel_user_followers:FOLLOWS]-(followers) +WHERE id(user) = 1012 +RETURN rel_follows; +

+ +

Dynamic Properties

+ +

```php +class Phone extends Eloquent {

+ +
public function user()
+{
+    return $this->belongsTo('User');
+}
+
+ +

}

+ +

$phone = Phone::find(1006); +$user = $phone->user; +// or getting an attribute out of the related model +$name = $phone->user->name; +```

+ +

Polymorphic

+ +

The concept behind Polymocrphic relations is purely relational to the bone but when it comes +to graph we are representing it as a HyperEdge.

+ +

Hyper edges involves three models, the parent model, hyper model and related model +represented in the following figure:

+ +

HyperEdges

+ +

Similarly in code this will be represented by three models User Comment and Post +where a User with id 1 posts a Post and a User with id 6 COMMENTED a Comment ON that Post +as follows:

+ +

```php +class User extends NeoEloquent {

+ +
public function comments($morph = null)
+{
+    return $this->hyperMorph($morph, 'Comment', 'COMMENTED', 'ON');
+}
+
+ +

} +```

+ +

In order to keep things simple but still involving the three models we will have to pass the +$morph which is any commentable model, in our case it's either a Video or a Post model.

+ +
+

Note: Make sure to have it defaulting to null so that we can Dynamicly or Eager load +with $user->comments later on.

+
+ +

Creating a Comment with the create() method.

+ +

```php +$user = User::find(6); +$post = Post::find(2);

+ +

$user->comments($post)->create(['text' => 'Totally agree!', 'likes' => 0, 'abuse' => 0]); +```

+ +

As usual we will have returned an Edge, but this time it's not directed it is an instance of +HyperEdge, read more about HyperEdges here.

+ +

Or you may save a Comment instance:

+ +

```php +$comment = new Comment(['text' => 'Magnificent', 'likes' => 0, 'abuse' => 0]);

+ +

$user->comments($post)->save($comment); +```

+ +

Also all the functionalities found in a BelongsToMany relationship are supported like +attaching models by Ids:

+ +

php +$user->comments($post)->attach([$id, $otherId]); +

+ +

Or detaching models:

+ +

php +$user->comments($post)->detach($comment); // or $comment->id +

+ +

Sync too:

+ +

php +$user->comments($post)->sync([$id, $otherId, $someId]); +

+ +

Retrieving Polymorphic Relations

+ +

From our previous example we will use the Video model to retrieve their comments:

+ +

```php +class Video extends NeoEloquent {

+ +
public function comments()
+{
+    return $this->morphMany('Comment', 'ON');
+}
+
+ +

} +```

+ +
Dynamicly Loading Morph Model
+ +

php +$video = Video::find(3); +$comments = $video->comments; +

+ +
Eager Loading Morph Model
+ +

php +$video = Video::with('comments')->find(3); +foreach ($video->comments as $comment) +{ + // +} +

+ +

Retrieving The Inverse of a Polymorphic Relation

+ +

```php +class Comment extends NeoEloquent {

+ +
public function commentable()
+{
+    return $this->morphTo();
+}
+
+ +

} +```

+ +

```php +$postComment = Comment::find(7); +$post = $comment->commentable;

+ +

$videoComment = Comment::find(5); +$video = $comment->commentable;

+ +

// You can also eager load them +Comment::with('commentable')->get(); +```

+ +

You may also specify the type of morph you would like returned:

+ +

```php +class Comment extends NeoEloquent {

+ +
public function post()
+{
+    return $this->morphTo('Post', 'ON');
+}
+
+public function video()
+{
+    return $this->morphTo('Video', 'ON');
+}
+
+ +

} +```

+ +

Polymorphic Relations In Short

+ +

To drill things down here's how our three models involved in a Polymorphic relationship connect:

+ +

```php +class User extends NeoEloquent {

+ +
public function comments($morph = null)
+{
+    return $this->hyperMorph($morph, 'Comment', 'COMMENTED', 'ON');
+}
+
+ +

} +```

+ +

```php +class Post extends NeoEloquent { // Video is the same as this one

+ +
public function comments()
+{
+    return $this->morphMany('Comment', 'ON');
+}
+
+ +

} +```

+ +

```php +class Comment extends NeoEloquent {

+ +
public function commentable()
+{
+    return $this->morphTo();
+}
+
+ +

}

+ +

```

+ +

Eager Loading

+ +

```php +class Book extends Eloquent {

+ +
public function author()
+{
+    return $this->belongsTo('Author');
+}
+
+ +

} +```

+ +

Loading authors with their books with the least performance overhead possible.

+ +

php +foreach (Book::with('author')->get() as $book) +{ + echo $book->author->name; +} +

+ +

Only two Cypher queries will be run in the loop above:

+ +

`` +MATCH (book:Book`) RETURN *;

+ +

MATCH (book:Book), (book)<-[:WROTE]-(author:Author) WHERE id(book) IN [1, 2, 3, 4, 5, ...] RETURN book, author; +```

+ +

Edges

+ + + +

Introduction

+ +

Due to the fact that relationships in Graph are much different than other database types so +we will have to handle them accordingly. Relationships have directions that can vary between +In and Out respectively towards the parent node.

+ +

EdgeIn

+ +

Represents an INCOMING direction relationship from the related model towards the parent model.

+ +

```php +class Location extends NeoEloquent {

+ +
public function user()
+{
+    return $this->belongsTo('User', 'LOCATED_AT');
+}
+
+ +

} +```

+ +

To associate a User to a Location:

+ +

php +$location = Location::find(1922); +$user = User::find(3876); +$relation = $location->associate($user); +

+ +

which in Cypher land will map to (:Location)<-[:LOCATED_AT]-(:User) and $relation +being an instance of EdgeIn representing an incoming relationship towards the parent.

+ +

And you can still access the models from the edge:

+ +

php +$relation = $location->associate($user); +$location = $relation->parent(); +$user = $relation->related(); +

+ +

EdgeOut

+ +

Represents an OUTGOING direction relationship from the parent model to the related model.

+ +

```php +class User extends NeoEloquent {

+ +
public function posts()
+{
+    return $this->hasMany('Post', 'POSTED');
+}
+
+ +

} +```

+ +

To save an outgoing edge from :User to :Post it goes like:

+ +

php +$post = new Post(['...']); +$posted = $user->posts()->save($post); +

+ +

Which in Cypher would be (:User)-[:POSTED]->(:Post) and $posted being the EdgeOut instance.

+ +

And fetch the related models:

+ +

php +$edge = $user->posts()->save($post); +$user = $edge->parent(); +$post = $edge->related(); +

+ +

HyperEdge

+ +

This edge comes as a result of a Polymorphic Relation representing an edge involving +two other edges left and right that can be accessed through the left() and right() methods.

+ +

This edge is treated a bit different than the others since it is not a direct relationship +between two models which means it has no specific direction.

+ +

```php +$edge = $user->comments($post)->attach($comment); +// Access the left and right edges +$left = $edge->left(); +$user = $left->parent(); +$comment = $left->related();

+ +

$right = $edge->right(); +$comment = $right->parent(); +$post = $right->related(); +```

+ +

Working With Edges

+ +

As stated earlier Edges are entities to Graph unlike SQL where they are a matter of a +foreign key having the value of the parent model as an attribute on the belonging model or in +Documents where they are either embeds or ids as references. So we developed them to be light +models which means you can work with them as if you were working with an Eloquent instance - to a certain extent, +except HyperEdges.

+ +

```php +// Create a new relationship +$relation = $location->associate($user); // Vinelab\NeoEloquent\Eloquent\Edges\EdgeIn

+ +

// Save the relationship to the database +$relation->save(); // true +```

+ +

In the case of a HyperEdge you can access all three models as follows:

+ +

php +$edge = $user->comments($post)->save($comment); +$user = $edge->parent(); +$comment = $edge->hyper(); +$post = $edge->related(); +

+ +

Edge Attributes

+ +

By default, edges will have the timestamps created_at and updated_at automatically set and updated only if timestamps are enabled by setting $timestamps to true +on the parent model.

+ +

```php +$locatedat = $location->associate($user); +$locatedat->since = 1966; +$locatedat->present = true; +$locatedat->save();

+ +

// $createdat and $updatedat are Carbon\Carbon instances +$createdat = $locatedat->createdat; +$updatedat = $locatedat->updatedat; +```

+ +
Retrieve an Edge from a Relation
+ +

The same way an association will create an EdgeIn relationship we can retrieve +the edge between two models by calling the edge($model) method on the belongsTo +relationship.

+ +

php +$location = Location::find(1892); +$edge = $location->user()->edge(); +

+ +

You may also specify the model at the other side of the edge.

+ +
+

Note: By default NeoEloquent will try to pefrorm the $location->user internally to figure +out the related side of the edge based on the relation function name, in this case it's +user().

+
+ +

php +$location = Location::find(1892); +$edge = $location->user()->edge($location->user); +

+ +

Only in Neo

+ + + +

Here you will find NeoEloquent-specific methods and implementations that with the +wonderful Eloquent methods would make working with Graph and Neo4j a blast!

+ +

CreateWith

+ + + +

This method will "kind of" fill the gap between relational and document databases, +it allows the creation of multiple related models with one database hit.

+ +

Creating New Records and Relations

+ +

Here's an example of creating a post with attached photos and videos:

+ +

```php +class Post extends NeoEloquent {

+ +
public function photos()
+{
+    return $this->hasMany('Photo', 'PHOTO');
+}
+
+public function videos()
+{
+    return $this->hasMany('Video', 'VIDEO');
+}
+
+ +

} +```

+ +

```php

+ +

Post::createWith(['title' => 'the title', 'body' => 'the body'], [ + 'photos' => [ + [ + 'url' => 'http://url', + 'caption' => '...', + 'metadata' => '...' + ], + [ + 'url' => 'http://other.url', + 'caption' => 'the bay', + 'metadata' => '...' + ] + ],

+ +
'videos' => [
+    'title' => 'Boats passing us by',
+    'description' => '...'
+]
+
+ +

]); +```

+ +
+

The keys photos and videos must be the same as the relation method names in the +Post model.

+
+ +

The Cypher query performed by the example above is:

+ +

+CREATE (post:`Post` {title: 'the title', body: 'the body'}), +(post)-[:PHOTO]->(:`Photo` {url: 'http://url', caption: '...', metadata: '...'}), +(post)-[:PHOTO]->(:`Photo` {url: 'http://other', caption: 'the bay', metadata: '...'}), +(post)-[:VIDEO]->(:`Video` {title: 'Boats passing us by', description: '...'}); +

+ +

We will get the nodes created with their relations as such:

+ +

CreateWith

+ +

You may also mix models and attributes as relation values but it is not necessary +since NeoEloquent will pass the provided attributes through the $fillable +filter pipeline:

+ +

php +$videos = new Video(['title' => 'foo', 'description' => 'bar']); +Post::createWith($info, compact('videos')); +

+ +

You may also use a single array of attributes as such:

+ +

```php +class User extends NeoEloquent {

+ +
public function account()
+{
+    return $this->hasOne('Account');
+}
+
+ +

}

+ +

User::createWith(['name' => 'foo'], ['account' => ['guid' => 'bar', 'email' => 'some@mail.net']]); +```

+ +

Attaching Existing Records as Relations

+ +

createWith is intelligent enough to know the difference when you pass an existing model, +a model Id or new records that you need to create which allows mixing new records with existing ones.

+ +

```php +class Post extends NeoEloquent {

+ +
public function tags()
+{
+    return $this->hasMany('Tag', 'TAG');
+}
+
+ +

} +```

+ +

```php +$tag1 = Tag::create(['title' => 'php']); +$tag2 = Tag::create(['title' => 'dev']);

+ +

$post = Post::createWith(['title' => 'foo', 'body' => 'bar'], ['tags' => [$tag1, $tag2]]); +```

+ +

And we will get the Post related to the existing Tag nodes.

+ +

Or using the id of the model:

+ +

php +Post::createWith(['title' => 'foo', 'body' => 'bar'], ['tags' => 1, 'privacy' => 2]); +

+ +

The Cypher for the query that attaches records would be:

+ +

+CREATE (post:`Post` {title: 'foo', 'body' => 'bar'}) +WITH post +MATCH (tag:`Tag`) +WHERE id(tag) IN [1, 2] +CREATE (post)-[:TAG]->(tag); +

+ +

Aggregates

+ +

In addition to the Eloquent builder aggregates, NeoEloquent also has support for +Neo4j specific aggregates like percentile and standard deviation, keeping the same +function names for convenience. +Check the docs for more.

+ +
+

table() represents the label of the model

+
+ +

``` +$users = DB::table('User')->count();

+ +

$distinct = DB::table('User')->countDistinct('points');

+ +

$price = DB::table('Order')->max('price');

+ +

$price = DB::table('Order')->min('price');

+ +

$price = DB::table('Order')->avg('price');

+ +

$total = DB::table('User')->sum('votes');

+ +

$disc = DB::table('User')->percentileDisc('votes', 0.2);

+ +

$cont = DB::table('User')->percentileCont('votes', 0.8);

+ +

$deviation = DB::table('User')->stdev('sex');

+ +

$population = DB::table('User')->stdevp('sex');

+ +

$emails = DB::table('User')->collect('email'); +```

+ +

Changelog

+ +

Check the Releases for details.

+ +

Avoid

+ +

Here are some constraints and Graph-specific gotchas, a list of features that are either not supported or not recommended.

+ +

JOINS :confounded:

+ + + +

Pivot Tables in Many-To-Many Relationships

+ +

This is not supported, instead we will be using Edges to work with relationships between models.

+ +

Nested Arrays and Objects

+ + + +

php +// Don't +User::create(['name' => 'Some Name', 'location' => ['lat' => 123, 'lng'=> -123 ] ]); +

+ +

Check out the createWith() method on how you can achieve this in a Graph way.

+ +

Tests

+ + + +
+

Tests marked as incomplete means they are either known issues or non-supported features, +check included messages for more info.

+
diff --git a/README.md b/README.md index 35a8a118..e1d35e7c 100644 --- a/README.md +++ b/README.md @@ -211,14 +211,6 @@ MERGE (account)<-[rel_user_account:ACCOUNT]-(user) RETURN rel_user_account; ``` -The Cypher performed by this statement will be as follows: - -``` -MATCH (phone:Phone) (phone)<-[:PHONE]-(user:User) -WHERE id(phone) = 1006 -RETURN user; -``` - ### One-To-Many ```php diff --git a/README.md.html b/README.md.html new file mode 100644 index 00000000..ebcca549 --- /dev/null +++ b/README.md.html @@ -0,0 +1,977 @@ + + + + + +
+

SensioLabsInsight

+ +

Build Status

+ +

+NeoEloquent

+ +

Neo4j Graph Eloquent Driver for Laravel 4

+ +

+Quick Reference

+ + + +

+Installation

+ +

Add the package to your composer.json and run composer update.

+ +
{
+    "require": {
+        "vinelab/neoeloquent": "*"
+    }
+}
+
+ +

Add the service provider in app/config/app.php:

+ +
'Vinelab\NeoEloquent\NeoEloquentServiceProvider',
+
+ +

The service provider will register all the required classes for this package and will also alias +the Model class to NeoEloquent so you can simply extend NeoEloquent in your models.

+ +

+Configuration

+ +

in app/config/database.php or in case of an environment-based configuration app/config/[env]/database.php +make neo4j your default connection:

+ +
'default' => 'neo4j',
+
+ +

Add the connection defaults:

+ +
'connections' => [
+    'neo4j' => [
+        'driver' => 'neo4j',
+        'host'   => 'localhost',
+        'port'   => '7474',
+        'username' => null,
+        'password' => null
+    ]
+]
+
+ +

+Documentation

+ +

+Models

+ + + +
class User extends NeoEloquent {}
+
+ +

As simple as it is, NeoEloquent will generate the default node label from the class name, +in this case it will be :User. Read about node labels here

+ +

+Namespaced Models

+ +

When you use namespaces with your models the label will consider the full namespace.

+ +
namespace Vinelab\Cms;
+
+class Admin extends NeoEloquent { }
+
+ +

The generated label from that relationship will be VinelabCmsAdmin, this is necessary to make sure +that labels do not clash in cases where we introduce another Admin instance like +Vinelab\Blog\Admin then things gets messy with :Admin in the database.

+ +

+Custom Node Labels

+ +

You may specify the label(s) you wish to be used instead of the default generated, they are also +case sensitive so they will be stored as put here.

+ +
class User extends NeoEloquent {
+
+    protected $label = 'User'; // or array('User', 'Fan')
+
+    protected $fillable = ['name', 'email'];
+}
+
+$user = User::create(['name' => 'Some Name', 'email' => 'some@email.com']);
+
+ +

NeoEloquent has a fallback support for the $table variable that will be used if found and there was no $label defined on the model.

+ +
class User extends NeoEloquent {
+
+    protected $table = 'User';
+
+}
+
+ +

Do not worry about the labels formatting, You may specify them as array('Label1', 'Label2') or separate them by a column : and prepending them with a : is optional.

+ +

+Soft Deleting

+ +

To enable soft deleting you'll need to use Vinelab\NeoEloquent\Eloquent\SoftDeletingTrait +instead of Illuminate\Database\Eloquent\SoftDeletingTrait and just like Eloquent you'll need the $dates in your models as follows:

+ +
use Vinelab\NeoEloquent\Eloquent\SoftDeletingTrait;
+
+class User extends NeoEloquent {
+
+    use SoftDeletingTrait;
+
+    protected $dates = ['deleted_at'];
+
+}
+
+ +

+Relationships

+ + + +

Let's go through some examples of relationships between Nodes.

+ +

+One-To-One

+ +
class User extends NeoEloquent {
+
+    public function phone()
+    {
+        return $this->hasOne('Phone');
+    }
+
+ +

This represents an OUTGOING relationship direction from the :User node to a :Phone.

+ +
+Saving
+ +
$phone = new Phone(['code' => 961, 'number' => '98765432'])
+$relation = $user->phone()->save($phone);
+
+ +

The Cypher performed by this statement will be as follows:

+ +
MATCH (user:`User`)
+WHERE id(user) = 1
+CREATE (user)-[:PHONE]->(phone:`Phone` {code: 961, number: '98765432', created_at: 7543788, updated_at: 7543788})
+RETURN phone;
+
+ +
+Defining The Inverse Of This Relation
+ +
class Phone extends NeoEloquent {
+
+    public function user()
+    {
+        return $this->belongsTo('User');
+    }
+}
+
+ +

This represents an INCOMING relationship direction from +the :User node to this :Phone node.

+ +
+Associating Models
+ +

Due to the fact that we do not deal with foreign keys, in our case it is much +more than just setting the foreign key attribute on the parent model. In Neo4j (and Graph in general) a relationship is an entity itself that can also have attributes of its own, hence the introduction of +Edges

+ +
+

Note: Associated models does not persist relations automatically when calling associate().

+
+ +
$account = Account::find(1986);
+
+// $relation will be Vinelab\NeoEloquent\Eloquent\Edges\EdgeIn
+$relation = $user->account()->associate($account);
+
+// Save the relation
+$relation->save();
+
+ +

The Cypher performed by this statement will be as follows:

+ +
MATCH (account:`Account`), (user:`User`)
+WHERE id(account) = 1986 AND id(user) = 9862
+MERGE (account)<-[rel_user_account:ACCOUNT]-(user)
+RETURN rel_user_account;
+
+ +

+One-To-Many

+ +
class User extends NeoEloquent {
+
+    public function posts()
+    {
+        return $this->hasMany('Post', 'POSTED');
+    }
+}
+
+ +

This represents an OUTGOING relationship direction +from the :User node to the :Post node.

+ +
$user = User::find(1);
+$post = new Post(['title' => 'The Title', 'body' => 'Hot Body']);
+$user->posts()->save($post);
+
+ +

Similar to One-To-One relationships the returned value from a save() statement is an +Edge[In|Out]

+ +

The Cypher performed by this statement will be as follows:

+ +
MATCH (user:`User`)
+WHERE id(user) = 1
+CREATE (user)-[rel_user_post:POSTED]->(post:`Post` {title: 'The Title', body: 'Hot Body', created_at: '15-05-2014', updated_at: '15-05-2014'})
+RETURN rel_user_post;
+
+ +
+Defining The Inverse Of This Relation
+ +
class Post extends NeoEloquent {
+
+    public function author()
+    {
+        return $this->belongsTo('User', 'POSTED');
+    }
+}
+
+ +

This represents an INCOMING relationship direction from +the :User node to this :Post node.

+ +

+Many-To-Many

+ +
class User extends NeoEloquent {
+
+    public function followers()
+    {
+        return $this->belongsToMany('User', 'FOLLOWS');
+    }
+}
+
+ +

This represents an OUTGOING relationship between a :User node and another :User.

+ +
$jd = User::find(1012);
+$mc = User::find(1013);
+
+ +

$jd follows $mc:

+ +
$jd->followers()->save($mc);
+
+ +

Or using the attach() method:

+ +
$jd->followers()->attach($mc);
+// Or..
+$jd->followers()->attach(1); // 1 being the id of $mc ($mc->getKey())
+
+ +

The Cypher performed by this statement will be as follows:

+ +
MATCH (user:`User`), (followers:`User`)
+WHERE id(user) = 1012 AND id(followers) = 1013
+CREATE (user)-[:FOLLOWS]->(followers)
+RETURN rel_follows;
+
+ +

$mc follows $jd back:

+ +
$mc->followers()->save($jd);
+
+ +

The Cypher performed by this statement will be as follows:

+ +
MATCH (user:`User`), (followers:`User`)
+WHERE id(user) = 1013 AND id(followers) = 1012
+CREATE (user)-[rel_user_followers:FOLLOWS]->(followers)
+RETURN rel_follows;
+
+ +

get the followers of $jd

+ +
$followers = $jd->followers;
+
+ +

The Cypher performed by this statement will be as follows:

+ +
MATCH (user:`User`), (followers:`User`), (user)-[rel_user_followers:FOLLOWS]-(followers)
+WHERE id(user) = 1012
+RETURN rel_follows;
+
+ +

+Dynamic Properties

+ +
class Phone extends Eloquent {
+
+    public function user()
+    {
+        return $this->belongsTo('User');
+    }
+
+}
+
+$phone = Phone::find(1006);
+$user = $phone->user;
+// or getting an attribute out of the related model
+$name = $phone->user->name;
+
+ +

+Polymorphic

+ +

The concept behind Polymocrphic relations is purely relational to the bone but when it comes +to graph we are representing it as a HyperEdge.

+ +

Hyper edges involves three models, the parent model, hyper model and related model +represented in the following figure:

+ +

HyperEdges

+ +

Similarly in code this will be represented by three models User Comment and Post +where a User with id 1 posts a Post and a User with id 6 COMMENTED a Comment ON that Post +as follows:

+ +
class User extends NeoEloquent {
+
+    public function comments($morph = null)
+    {
+        return $this->hyperMorph($morph, 'Comment', 'COMMENTED', 'ON');
+    }
+
+}
+
+ +

In order to keep things simple but still involving the three models we will have to pass the +$morph which is any commentable model, in our case it's either a Video or a Post model.

+ +
+

Note: Make sure to have it defaulting to null so that we can Dynamicly or Eager load +with $user->comments later on.

+
+ +

Creating a Comment with the create() method.

+ +
$user = User::find(6);
+$post = Post::find(2);
+
+$user->comments($post)->create(['text' => 'Totally agree!', 'likes' => 0, 'abuse' => 0]);
+
+ +

As usual we will have returned an Edge, but this time it's not directed it is an instance of +HyperEdge, read more about HyperEdges here.

+ +

Or you may save a Comment instance:

+ +
$comment = new Comment(['text' => 'Magnificent', 'likes' => 0, 'abuse' => 0]);
+
+$user->comments($post)->save($comment);
+
+ +

Also all the functionalities found in a BelongsToMany relationship are supported like +attaching models by Ids:

+ +
$user->comments($post)->attach([$id, $otherId]);
+
+ +

Or detaching models:

+ +
$user->comments($post)->detach($comment); // or $comment->id
+
+ +

Sync too:

+ +
$user->comments($post)->sync([$id, $otherId, $someId]);
+
+ +

+Retrieving Polymorphic Relations

+ +

From our previous example we will use the Video model to retrieve their comments:

+ +
class Video extends NeoEloquent {
+
+    public function comments()
+    {
+        return $this->morphMany('Comment', 'ON');
+    }
+
+}
+
+ +
+Dynamicly Loading Morph Model
+ +
$video = Video::find(3);
+$comments = $video->comments;
+
+ +
+Eager Loading Morph Model
+ +
$video = Video::with('comments')->find(3);
+foreach ($video->comments as $comment)
+{
+    //
+}
+
+ +

+Retrieving The Inverse of a Polymorphic Relation

+ +
class Comment extends NeoEloquent {
+
+    public function commentable()
+    {
+        return $this->morphTo();
+    }
+
+}
+
+ +
$postComment = Comment::find(7);
+$post = $comment->commentable;
+
+$videoComment = Comment::find(5);
+$video = $comment->commentable;
+
+// You can also eager load them
+Comment::with('commentable')->get();
+
+ +

You may also specify the type of morph you would like returned:

+ +
class Comment extends NeoEloquent {
+
+    public function post()
+    {
+        return $this->morphTo('Post', 'ON');
+    }
+
+    public function video()
+    {
+        return $this->morphTo('Video', 'ON');
+    }
+
+}
+
+ +

+Polymorphic Relations In Short

+ +

To drill things down here's how our three models involved in a Polymorphic relationship connect:

+ +
class User extends NeoEloquent {
+
+    public function comments($morph = null)
+    {
+        return $this->hyperMorph($morph, 'Comment', 'COMMENTED', 'ON');
+    }
+
+}
+
+ +
class Post extends NeoEloquent { // Video is the same as this one
+
+    public function comments()
+    {
+        return $this->morphMany('Comment', 'ON');
+    }
+
+}
+
+ +
class Comment extends NeoEloquent {
+
+    public function commentable()
+    {
+        return $this->morphTo();
+    }
+
+}
+
+
+ +

+Eager Loading

+ +
class Book extends Eloquent {
+
+    public function author()
+    {
+        return $this->belongsTo('Author');
+    }
+}
+
+ +

Loading authors with their books with the least performance overhead possible.

+ +
foreach (Book::with('author')->get() as $book)
+{
+    echo $book->author->name;
+}
+
+ +

Only two Cypher queries will be run in the loop above:

+ +
MATCH (book:`Book`) RETURN *;
+
+MATCH (book:`Book`), (book)<-[:WROTE]-(author:`Author`) WHERE id(book) IN [1, 2, 3, 4, 5, ...] RETURN book, author;
+
+ +

+Edges

+ + + +

+Introduction

+ +

Due to the fact that relationships in Graph are much different than other database types so +we will have to handle them accordingly. Relationships have directions that can vary between +In and Out respectively towards the parent node.

+ +

+EdgeIn

+ +

Represents an INCOMING direction relationship from the related model towards the parent model.

+ +
class Location extends NeoEloquent {
+
+    public function user()
+    {
+        return $this->belongsTo('User', 'LOCATED_AT');
+    }
+
+}
+
+ +

To associate a User to a Location:

+ +
$location = Location::find(1922);
+$user = User::find(3876);
+$relation = $location->associate($user);
+
+ +

which in Cypher land will map to (:Location)<-[:LOCATED_AT]-(:User) and $relation +being an instance of EdgeIn representing an incoming relationship towards the parent.

+ +

And you can still access the models from the edge:

+ +
$relation = $location->associate($user);
+$location = $relation->parent();
+$user = $relation->related();
+
+ +

+EdgeOut

+ +

Represents an OUTGOING direction relationship from the parent model to the related model.

+ +
class User extends NeoEloquent {
+
+    public function posts()
+    {
+        return $this->hasMany('Post', 'POSTED');
+    }
+
+}
+
+ +

To save an outgoing edge from :User to :Post it goes like:

+ +
$post = new Post(['...']);
+$posted = $user->posts()->save($post);
+
+ +

Which in Cypher would be (:User)-[:POSTED]->(:Post) and $posted being the EdgeOut instance.

+ +

And fetch the related models:

+ +
$edge = $user->posts()->save($post);
+$user = $edge->parent();
+$post = $edge->related();
+
+ +

+HyperEdge

+ +

This edge comes as a result of a Polymorphic Relation representing an edge involving +two other edges left and right that can be accessed through the left() and right() methods.

+ +

This edge is treated a bit different than the others since it is not a direct relationship +between two models which means it has no specific direction.

+ +
$edge = $user->comments($post)->attach($comment);
+// Access the left and right edges
+$left = $edge->left();
+$user = $left->parent();
+$comment = $left->related();
+
+$right = $edge->right();
+$comment = $right->parent();
+$post = $right->related();
+
+ +

+Working With Edges

+ +

As stated earlier Edges are entities to Graph unlike SQL where they are a matter of a +foreign key having the value of the parent model as an attribute on the belonging model or in +Documents where they are either embeds or ids as references. So we developed them to be light +models which means you can work with them as if you were working with an Eloquent instance - to a certain extent, +except HyperEdges.

+ +
// Create a new relationship
+$relation = $location->associate($user); // Vinelab\NeoEloquent\Eloquent\Edges\EdgeIn
+
+// Save the relationship to the database
+$relation->save(); // true
+
+ +

In the case of a HyperEdge you can access all three models as follows:

+ +
$edge    = $user->comments($post)->save($comment);
+$user    = $edge->parent();
+$comment = $edge->hyper();
+$post    = $edge->related();
+
+ +

+Edge Attributes

+ +

By default, edges will have the timestamps created_at and updated_at automatically set and updated only if timestamps are enabled by setting $timestamps to true +on the parent model.

+ +
$located_at = $location->associate($user);
+$located_at->since = 1966;
+$located_at->present = true;
+$located_at->save();
+
+// $created_at and $updated_at are Carbon\Carbon instances
+$created_at = $located_at->created_at;
+$updated_at = $located_at->updated_at;
+
+ +
+Retrieve an Edge from a Relation
+ +

The same way an association will create an EdgeIn relationship we can retrieve +the edge between two models by calling the edge($model) method on the belongsTo +relationship.

+ +
$location = Location::find(1892);
+$edge = $location->user()->edge();
+
+ +

You may also specify the model at the other side of the edge.

+ +
+

Note: By default NeoEloquent will try to pefrorm the $location->user internally to figure +out the related side of the edge based on the relation function name, in this case it's +user().

+
+ +
$location = Location::find(1892);
+$edge = $location->user()->edge($location->user);
+
+ +

+Only in Neo

+ + + +

Here you will find NeoEloquent-specific methods and implementations that with the +wonderful Eloquent methods would make working with Graph and Neo4j a blast!

+ +

+CreateWith

+ + + +

This method will "kind of" fill the gap between relational and document databases, +it allows the creation of multiple related models with one database hit.

+ +

+Creating New Records and Relations

+ +

Here's an example of creating a post with attached photos and videos:

+ +
class Post extends NeoEloquent {
+
+    public function photos()
+    {
+        return $this->hasMany('Photo', 'PHOTO');
+    }
+
+    public function videos()
+    {
+        return $this->hasMany('Video', 'VIDEO');
+    }
+}
+
+ +

+Post::createWith(['title' => 'the title', 'body' => 'the body'], [
+    'photos' => [
+        [
+            'url'      => 'http://url',
+            'caption'  => '...',
+            'metadata' => '...'
+        ],
+        [
+            'url' => 'http://other.url',
+            'caption' => 'the bay',
+            'metadata' => '...'
+        ]
+    ],
+
+    'videos' => [
+        'title' => 'Boats passing us by',
+        'description' => '...'
+    ]
+]);
+
+ +
+

The keys photos and videos must be the same as the relation method names in the +Post model.

+
+ +

The Cypher query performed by the example above is:

+ +
CREATE (post:`Post` {title: 'the title', body: 'the body'}),
+(post)-[:PHOTO]->(:`Photo` {url: 'http://url', caption: '...', metadata: '...'}),
+(post)-[:PHOTO]->(:`Photo` {url: 'http://other', caption: 'the bay', metadata: '...'}),
+(post)-[:VIDEO]->(:`Video` {title: 'Boats passing us by', description: '...'});
+
+ +

We will get the nodes created with their relations as such:

+ +

CreateWith

+ +

You may also mix models and attributes as relation values but it is not necessary +since NeoEloquent will pass the provided attributes through the $fillable +filter pipeline:

+ +
$videos = new Video(['title' => 'foo', 'description' => 'bar']);
+Post::createWith($info, compact('videos'));
+
+ +

You may also use a single array of attributes as such:

+ +
class User extends NeoEloquent {
+
+    public function account()
+    {
+        return $this->hasOne('Account');
+    }
+}
+
+User::createWith(['name' => 'foo'], ['account' => ['guid' => 'bar', 'email' => 'some@mail.net']]);
+
+ +

+Attaching Existing Records as Relations

+ +

createWith is intelligent enough to know the difference when you pass an existing model, +a model Id or new records that you need to create which allows mixing new records with existing ones.

+ +
class Post extends NeoEloquent {
+
+    public function tags()
+    {
+        return $this->hasMany('Tag', 'TAG');
+    }
+}
+
+ +
$tag1 = Tag::create(['title' => 'php']);
+$tag2 = Tag::create(['title' => 'dev']);
+
+$post = Post::createWith(['title' => 'foo', 'body' => 'bar'], ['tags' => [$tag1, $tag2]]);
+
+ +

And we will get the Post related to the existing Tag nodes.

+ +

Or using the id of the model:

+ +
Post::createWith(['title' => 'foo', 'body' => 'bar'], ['tags' => 1, 'privacy' => 2]);
+
+ +

The Cypher for the query that attaches records would be:

+ +
CREATE (post:`Post` {title: 'foo', 'body' => 'bar'})
+WITH post
+MATCH (tag:`Tag`)
+WHERE id(tag) IN [1, 2]
+CREATE (post)-[:TAG]->(tag);
+
+ +

+Aggregates

+ +

In addition to the Eloquent builder aggregates, NeoEloquent also has support for +Neo4j specific aggregates like percentile and standard deviation, keeping the same +function names for convenience. +Check the docs for more.

+ +
+

table() represents the label of the model

+
+ +
$users = DB::table('User')->count();
+
+$distinct = DB::table('User')->countDistinct('points');
+
+$price = DB::table('Order')->max('price');
+
+$price = DB::table('Order')->min('price');
+
+$price = DB::table('Order')->avg('price');
+
+$total = DB::table('User')->sum('votes');
+
+$disc = DB::table('User')->percentileDisc('votes', 0.2);
+
+$cont = DB::table('User')->percentileCont('votes', 0.8);
+
+$deviation = DB::table('User')->stdev('sex');
+
+$population = DB::table('User')->stdevp('sex');
+
+$emails = DB::table('User')->collect('email');
+
+ +

+Changelog

+ +

Check the Releases for details.

+ +

+Avoid

+ +

Here are some constraints and Graph-specific gotchas, a list of features that are either not supported or not recommended.

+ +

+JOINS :confounded: +

+ +
    +
  • They make no sense for Graph, plus Graph hates them! +Which makes them unsupported on purpose. If migrating from an SQL-based app +they will be your boogie monster.
  • +
+ +

+Pivot Tables in Many-To-Many Relationships

+ +

This is not supported, instead we will be using Edges to work with relationships between models.

+ +

+Nested Arrays and Objects

+ +
    +
  • Due to the limitations imposed by the objects map types that can be stored in a single, +you can never have nested arrays or objects in a single model, +make sure it's flat. Example: +
  • +
+ +
// Don't
+User::create(['name' => 'Some Name', 'location' => ['lat' => 123, 'lng'=> -123 ] ]);
+
+ +

Check out the createWith() method on how you can achieve this in a Graph way.

+ +

+Tests

+ +
    +
  • install a Neo4j instance and run it with the default configuration localhost:7474 +
  • +
  • make sure the database graph is empty to avoid conflicts
  • +
  • after running composer install there should be /vendor/bin/phpunit +
  • +
  • run ./vendor/bin/phpunit after making sure that the Neo4j instance is running
  • +
+ +
+

Tests marked as incomplete means they are either known issues or non-supported features, +check included messages for more info.

+
+
+ From b45650279c13585ef7916d460517e80152d566ac Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Fri, 30 Jan 2015 10:33:21 +0200 Subject: [PATCH 3/9] Didn't mean to check this in --- README.html | 1007 --------------------------------------------------- 1 file changed, 1007 deletions(-) delete mode 100644 README.html diff --git a/README.html b/README.html deleted file mode 100644 index cff223a7..00000000 --- a/README.html +++ /dev/null @@ -1,1007 +0,0 @@ -

SensioLabsInsight

- -

Build Status

- -

NeoEloquent

- -

Neo4j Graph Eloquent Driver for Laravel 4

- -

Quick Reference

- - - -

Installation

- -

Add the package to your composer.json and run composer update.

- -

json -{ - "require": { - "vinelab/neoeloquent": "*" - } -} -

- -

Add the service provider in app/config/app.php:

- -

php -'Vinelab\NeoEloquent\NeoEloquentServiceProvider', -

- -

The service provider will register all the required classes for this package and will also alias -the Model class to NeoEloquent so you can simply extend NeoEloquent in your models.

- -

Configuration

- -

in app/config/database.php or in case of an environment-based configuration app/config/[env]/database.php -make neo4j your default connection:

- -

php -'default' => 'neo4j', -

- -

Add the connection defaults:

- -

php -'connections' => [ - 'neo4j' => [ - 'driver' => 'neo4j', - 'host' => 'localhost', - 'port' => '7474', - 'username' => null, - 'password' => null - ] -] -

- -

Documentation

- -

Models

- - - -

php -class User extends NeoEloquent {} -

- -

As simple as it is, NeoEloquent will generate the default node label from the class name, -in this case it will be :User. Read about node labels here

- -

Namespaced Models

- -

When you use namespaces with your models the label will consider the full namespace.

- -

```php -namespace Vinelab\Cms;

- -

class Admin extends NeoEloquent { } -```

- -

The generated label from that relationship will be VinelabCmsAdmin, this is necessary to make sure -that labels do not clash in cases where we introduce another Admin instance like -Vinelab\Blog\Admin then things gets messy with :Admin in the database.

- -

Custom Node Labels

- -

You may specify the label(s) you wish to be used instead of the default generated, they are also -case sensitive so they will be stored as put here.

- -

```php -class User extends NeoEloquent {

- -
protected $label = 'User'; // or array('User', 'Fan')
-
-protected $fillable = ['name', 'email'];
-
- -

}

- -

$user = User::create(['name' => 'Some Name', 'email' => 'some@email.com']); -```

- -

NeoEloquent has a fallback support for the $table variable that will be used if found and there was no $label defined on the model.

- -

```php -class User extends NeoEloquent {

- -
protected $table = 'User';
-
- -

} -```

- -

Do not worry about the labels formatting, You may specify them as array('Label1', 'Label2') or separate them by a column : and prepending them with a : is optional.

- -

Soft Deleting

- -

To enable soft deleting you'll need to use Vinelab\NeoEloquent\Eloquent\SoftDeletingTrait -instead of Illuminate\Database\Eloquent\SoftDeletingTrait and just like Eloquent you'll need the $dates in your models as follows:

- -

```php -use Vinelab\NeoEloquent\Eloquent\SoftDeletingTrait;

- -

class User extends NeoEloquent {

- -
use SoftDeletingTrait;
-
-protected $dates = ['deleted_at'];
-
- -

} -```

- -

Relationships

- - - -

Let's go through some examples of relationships between Nodes.

- -

One-To-One

- -

```php -class User extends NeoEloquent {

- -
public function phone()
-{
-    return $this->hasOne('Phone');
-}
-
- -

`` -This represents anOUTGOINGrelationship direction from the:Usernode to a:Phone`.

- -
Saving
- -

php -$phone = new Phone(['code' => 961, 'number' => '98765432']) -$relation = $user->phone()->save($phone); -

- -

The Cypher performed by this statement will be as follows:

- -

-MATCH (user:`User`) -WHERE id(user) = 1 -CREATE (user)-[:PHONE]->(phone:`Phone` {code: 961, number: '98765432', created_at: 7543788, updated_at: 7543788}) -RETURN phone; -

- -
Defining The Inverse Of This Relation
- -

```php -class Phone extends NeoEloquent {

- -
public function user()
-{
-    return $this->belongsTo('User');
-}
-
- -

} -```

- -

This represents an INCOMING relationship direction from -the :User node to this :Phone node.

- -
Associating Models
- -

Due to the fact that we do not deal with foreign keys, in our case it is much -more than just setting the foreign key attribute on the parent model. In Neo4j (and Graph in general) a relationship is an entity itself that can also have attributes of its own, hence the introduction of -Edges

- -
-

Note: Associated models does not persist relations automatically when calling associate().

-
- -

```php -$account = Account::find(1986);

- -

// $relation will be Vinelab\NeoEloquent\Eloquent\Edges\EdgeIn -$relation = $user->account()->associate($account);

- -

// Save the relation -$relation->save(); -```

- -

The Cypher performed by this statement will be as follows:

- -

-MATCH (account:`Account`), (user:`User`) -WHERE id(account) = 1986 AND id(user) = 9862 -MERGE (account)<-[rel_user_account:ACCOUNT]-(user) -RETURN rel_user_account; -

- -

The Cypher performed by this statement will be as follows:

- -

-MATCH (phone:Phone) (phone)<-[:PHONE]-(user:User) -WHERE id(phone) = 1006 -RETURN user; -

- -

One-To-Many

- -

```php -class User extends NeoEloquent {

- -
public function posts()
-{
-    return $this->hasMany('Post', 'POSTED');
-}
-
- -

} -```

- -

This represents an OUTGOING relationship direction -from the :User node to the :Post node.

- -

php -$user = User::find(1); -$post = new Post(['title' => 'The Title', 'body' => 'Hot Body']); -$user->posts()->save($post); -

- -

Similar to One-To-One relationships the returned value from a save() statement is an -Edge[In|Out]

- -

The Cypher performed by this statement will be as follows:

- -

-MATCH (user:`User`) -WHERE id(user) = 1 -CREATE (user)-[rel_user_post:POSTED]->(post:`Post` {title: 'The Title', body: 'Hot Body', created_at: '15-05-2014', updated_at: '15-05-2014'}) -RETURN rel_user_post; -

- -
Defining The Inverse Of This Relation
- -

```php -class Post extends NeoEloquent {

- -
public function author()
-{
-    return $this->belongsTo('User', 'POSTED');
-}
-
- -

} -```

- -

This represents an INCOMING relationship direction from -the :User node to this :Post node.

- -

Many-To-Many

- -

```php -class User extends NeoEloquent {

- -
public function followers()
-{
-    return $this->belongsToMany('User', 'FOLLOWS');
-}
-
- -

} -```

- -

This represents an OUTGOING relationship between a :User node and another :User.

- -

php -$jd = User::find(1012); -$mc = User::find(1013); -

- -

$jd follows $mc:

- -

php -$jd->followers()->save($mc); -

- -

Or using the attach() method:

- -

php -$jd->followers()->attach($mc); -// Or.. -$jd->followers()->attach(1); // 1 being the id of $mc ($mc->getKey()) -

- -

The Cypher performed by this statement will be as follows:

- -

-MATCH (user:`User`), (followers:`User`) -WHERE id(user) = 1012 AND id(followers) = 1013 -CREATE (user)-[:FOLLOWS]->(followers) -RETURN rel_follows; -

- -

$mc follows $jd back:

- -

php -$mc->followers()->save($jd); -

- -

The Cypher performed by this statement will be as follows:

- -

-MATCH (user:`User`), (followers:`User`) -WHERE id(user) = 1013 AND id(followers) = 1012 -CREATE (user)-[rel_user_followers:FOLLOWS]->(followers) -RETURN rel_follows; -

- -

get the followers of $jd

- -

php -$followers = $jd->followers; -

- -

The Cypher performed by this statement will be as follows:

- -

-MATCH (user:`User`), (followers:`User`), (user)-[rel_user_followers:FOLLOWS]-(followers) -WHERE id(user) = 1012 -RETURN rel_follows; -

- -

Dynamic Properties

- -

```php -class Phone extends Eloquent {

- -
public function user()
-{
-    return $this->belongsTo('User');
-}
-
- -

}

- -

$phone = Phone::find(1006); -$user = $phone->user; -// or getting an attribute out of the related model -$name = $phone->user->name; -```

- -

Polymorphic

- -

The concept behind Polymocrphic relations is purely relational to the bone but when it comes -to graph we are representing it as a HyperEdge.

- -

Hyper edges involves three models, the parent model, hyper model and related model -represented in the following figure:

- -

HyperEdges

- -

Similarly in code this will be represented by three models User Comment and Post -where a User with id 1 posts a Post and a User with id 6 COMMENTED a Comment ON that Post -as follows:

- -

```php -class User extends NeoEloquent {

- -
public function comments($morph = null)
-{
-    return $this->hyperMorph($morph, 'Comment', 'COMMENTED', 'ON');
-}
-
- -

} -```

- -

In order to keep things simple but still involving the three models we will have to pass the -$morph which is any commentable model, in our case it's either a Video or a Post model.

- -
-

Note: Make sure to have it defaulting to null so that we can Dynamicly or Eager load -with $user->comments later on.

-
- -

Creating a Comment with the create() method.

- -

```php -$user = User::find(6); -$post = Post::find(2);

- -

$user->comments($post)->create(['text' => 'Totally agree!', 'likes' => 0, 'abuse' => 0]); -```

- -

As usual we will have returned an Edge, but this time it's not directed it is an instance of -HyperEdge, read more about HyperEdges here.

- -

Or you may save a Comment instance:

- -

```php -$comment = new Comment(['text' => 'Magnificent', 'likes' => 0, 'abuse' => 0]);

- -

$user->comments($post)->save($comment); -```

- -

Also all the functionalities found in a BelongsToMany relationship are supported like -attaching models by Ids:

- -

php -$user->comments($post)->attach([$id, $otherId]); -

- -

Or detaching models:

- -

php -$user->comments($post)->detach($comment); // or $comment->id -

- -

Sync too:

- -

php -$user->comments($post)->sync([$id, $otherId, $someId]); -

- -

Retrieving Polymorphic Relations

- -

From our previous example we will use the Video model to retrieve their comments:

- -

```php -class Video extends NeoEloquent {

- -
public function comments()
-{
-    return $this->morphMany('Comment', 'ON');
-}
-
- -

} -```

- -
Dynamicly Loading Morph Model
- -

php -$video = Video::find(3); -$comments = $video->comments; -

- -
Eager Loading Morph Model
- -

php -$video = Video::with('comments')->find(3); -foreach ($video->comments as $comment) -{ - // -} -

- -

Retrieving The Inverse of a Polymorphic Relation

- -

```php -class Comment extends NeoEloquent {

- -
public function commentable()
-{
-    return $this->morphTo();
-}
-
- -

} -```

- -

```php -$postComment = Comment::find(7); -$post = $comment->commentable;

- -

$videoComment = Comment::find(5); -$video = $comment->commentable;

- -

// You can also eager load them -Comment::with('commentable')->get(); -```

- -

You may also specify the type of morph you would like returned:

- -

```php -class Comment extends NeoEloquent {

- -
public function post()
-{
-    return $this->morphTo('Post', 'ON');
-}
-
-public function video()
-{
-    return $this->morphTo('Video', 'ON');
-}
-
- -

} -```

- -

Polymorphic Relations In Short

- -

To drill things down here's how our three models involved in a Polymorphic relationship connect:

- -

```php -class User extends NeoEloquent {

- -
public function comments($morph = null)
-{
-    return $this->hyperMorph($morph, 'Comment', 'COMMENTED', 'ON');
-}
-
- -

} -```

- -

```php -class Post extends NeoEloquent { // Video is the same as this one

- -
public function comments()
-{
-    return $this->morphMany('Comment', 'ON');
-}
-
- -

} -```

- -

```php -class Comment extends NeoEloquent {

- -
public function commentable()
-{
-    return $this->morphTo();
-}
-
- -

}

- -

```

- -

Eager Loading

- -

```php -class Book extends Eloquent {

- -
public function author()
-{
-    return $this->belongsTo('Author');
-}
-
- -

} -```

- -

Loading authors with their books with the least performance overhead possible.

- -

php -foreach (Book::with('author')->get() as $book) -{ - echo $book->author->name; -} -

- -

Only two Cypher queries will be run in the loop above:

- -

`` -MATCH (book:Book`) RETURN *;

- -

MATCH (book:Book), (book)<-[:WROTE]-(author:Author) WHERE id(book) IN [1, 2, 3, 4, 5, ...] RETURN book, author; -```

- -

Edges

- - - -

Introduction

- -

Due to the fact that relationships in Graph are much different than other database types so -we will have to handle them accordingly. Relationships have directions that can vary between -In and Out respectively towards the parent node.

- -

EdgeIn

- -

Represents an INCOMING direction relationship from the related model towards the parent model.

- -

```php -class Location extends NeoEloquent {

- -
public function user()
-{
-    return $this->belongsTo('User', 'LOCATED_AT');
-}
-
- -

} -```

- -

To associate a User to a Location:

- -

php -$location = Location::find(1922); -$user = User::find(3876); -$relation = $location->associate($user); -

- -

which in Cypher land will map to (:Location)<-[:LOCATED_AT]-(:User) and $relation -being an instance of EdgeIn representing an incoming relationship towards the parent.

- -

And you can still access the models from the edge:

- -

php -$relation = $location->associate($user); -$location = $relation->parent(); -$user = $relation->related(); -

- -

EdgeOut

- -

Represents an OUTGOING direction relationship from the parent model to the related model.

- -

```php -class User extends NeoEloquent {

- -
public function posts()
-{
-    return $this->hasMany('Post', 'POSTED');
-}
-
- -

} -```

- -

To save an outgoing edge from :User to :Post it goes like:

- -

php -$post = new Post(['...']); -$posted = $user->posts()->save($post); -

- -

Which in Cypher would be (:User)-[:POSTED]->(:Post) and $posted being the EdgeOut instance.

- -

And fetch the related models:

- -

php -$edge = $user->posts()->save($post); -$user = $edge->parent(); -$post = $edge->related(); -

- -

HyperEdge

- -

This edge comes as a result of a Polymorphic Relation representing an edge involving -two other edges left and right that can be accessed through the left() and right() methods.

- -

This edge is treated a bit different than the others since it is not a direct relationship -between two models which means it has no specific direction.

- -

```php -$edge = $user->comments($post)->attach($comment); -// Access the left and right edges -$left = $edge->left(); -$user = $left->parent(); -$comment = $left->related();

- -

$right = $edge->right(); -$comment = $right->parent(); -$post = $right->related(); -```

- -

Working With Edges

- -

As stated earlier Edges are entities to Graph unlike SQL where they are a matter of a -foreign key having the value of the parent model as an attribute on the belonging model or in -Documents where they are either embeds or ids as references. So we developed them to be light -models which means you can work with them as if you were working with an Eloquent instance - to a certain extent, -except HyperEdges.

- -

```php -// Create a new relationship -$relation = $location->associate($user); // Vinelab\NeoEloquent\Eloquent\Edges\EdgeIn

- -

// Save the relationship to the database -$relation->save(); // true -```

- -

In the case of a HyperEdge you can access all three models as follows:

- -

php -$edge = $user->comments($post)->save($comment); -$user = $edge->parent(); -$comment = $edge->hyper(); -$post = $edge->related(); -

- -

Edge Attributes

- -

By default, edges will have the timestamps created_at and updated_at automatically set and updated only if timestamps are enabled by setting $timestamps to true -on the parent model.

- -

```php -$locatedat = $location->associate($user); -$locatedat->since = 1966; -$locatedat->present = true; -$locatedat->save();

- -

// $createdat and $updatedat are Carbon\Carbon instances -$createdat = $locatedat->createdat; -$updatedat = $locatedat->updatedat; -```

- -
Retrieve an Edge from a Relation
- -

The same way an association will create an EdgeIn relationship we can retrieve -the edge between two models by calling the edge($model) method on the belongsTo -relationship.

- -

php -$location = Location::find(1892); -$edge = $location->user()->edge(); -

- -

You may also specify the model at the other side of the edge.

- -
-

Note: By default NeoEloquent will try to pefrorm the $location->user internally to figure -out the related side of the edge based on the relation function name, in this case it's -user().

-
- -

php -$location = Location::find(1892); -$edge = $location->user()->edge($location->user); -

- -

Only in Neo

- - - -

Here you will find NeoEloquent-specific methods and implementations that with the -wonderful Eloquent methods would make working with Graph and Neo4j a blast!

- -

CreateWith

- - - -

This method will "kind of" fill the gap between relational and document databases, -it allows the creation of multiple related models with one database hit.

- -

Creating New Records and Relations

- -

Here's an example of creating a post with attached photos and videos:

- -

```php -class Post extends NeoEloquent {

- -
public function photos()
-{
-    return $this->hasMany('Photo', 'PHOTO');
-}
-
-public function videos()
-{
-    return $this->hasMany('Video', 'VIDEO');
-}
-
- -

} -```

- -

```php

- -

Post::createWith(['title' => 'the title', 'body' => 'the body'], [ - 'photos' => [ - [ - 'url' => 'http://url', - 'caption' => '...', - 'metadata' => '...' - ], - [ - 'url' => 'http://other.url', - 'caption' => 'the bay', - 'metadata' => '...' - ] - ],

- -
'videos' => [
-    'title' => 'Boats passing us by',
-    'description' => '...'
-]
-
- -

]); -```

- -
-

The keys photos and videos must be the same as the relation method names in the -Post model.

-
- -

The Cypher query performed by the example above is:

- -

-CREATE (post:`Post` {title: 'the title', body: 'the body'}), -(post)-[:PHOTO]->(:`Photo` {url: 'http://url', caption: '...', metadata: '...'}), -(post)-[:PHOTO]->(:`Photo` {url: 'http://other', caption: 'the bay', metadata: '...'}), -(post)-[:VIDEO]->(:`Video` {title: 'Boats passing us by', description: '...'}); -

- -

We will get the nodes created with their relations as such:

- -

CreateWith

- -

You may also mix models and attributes as relation values but it is not necessary -since NeoEloquent will pass the provided attributes through the $fillable -filter pipeline:

- -

php -$videos = new Video(['title' => 'foo', 'description' => 'bar']); -Post::createWith($info, compact('videos')); -

- -

You may also use a single array of attributes as such:

- -

```php -class User extends NeoEloquent {

- -
public function account()
-{
-    return $this->hasOne('Account');
-}
-
- -

}

- -

User::createWith(['name' => 'foo'], ['account' => ['guid' => 'bar', 'email' => 'some@mail.net']]); -```

- -

Attaching Existing Records as Relations

- -

createWith is intelligent enough to know the difference when you pass an existing model, -a model Id or new records that you need to create which allows mixing new records with existing ones.

- -

```php -class Post extends NeoEloquent {

- -
public function tags()
-{
-    return $this->hasMany('Tag', 'TAG');
-}
-
- -

} -```

- -

```php -$tag1 = Tag::create(['title' => 'php']); -$tag2 = Tag::create(['title' => 'dev']);

- -

$post = Post::createWith(['title' => 'foo', 'body' => 'bar'], ['tags' => [$tag1, $tag2]]); -```

- -

And we will get the Post related to the existing Tag nodes.

- -

Or using the id of the model:

- -

php -Post::createWith(['title' => 'foo', 'body' => 'bar'], ['tags' => 1, 'privacy' => 2]); -

- -

The Cypher for the query that attaches records would be:

- -

-CREATE (post:`Post` {title: 'foo', 'body' => 'bar'}) -WITH post -MATCH (tag:`Tag`) -WHERE id(tag) IN [1, 2] -CREATE (post)-[:TAG]->(tag); -

- -

Aggregates

- -

In addition to the Eloquent builder aggregates, NeoEloquent also has support for -Neo4j specific aggregates like percentile and standard deviation, keeping the same -function names for convenience. -Check the docs for more.

- -
-

table() represents the label of the model

-
- -

``` -$users = DB::table('User')->count();

- -

$distinct = DB::table('User')->countDistinct('points');

- -

$price = DB::table('Order')->max('price');

- -

$price = DB::table('Order')->min('price');

- -

$price = DB::table('Order')->avg('price');

- -

$total = DB::table('User')->sum('votes');

- -

$disc = DB::table('User')->percentileDisc('votes', 0.2);

- -

$cont = DB::table('User')->percentileCont('votes', 0.8);

- -

$deviation = DB::table('User')->stdev('sex');

- -

$population = DB::table('User')->stdevp('sex');

- -

$emails = DB::table('User')->collect('email'); -```

- -

Changelog

- -

Check the Releases for details.

- -

Avoid

- -

Here are some constraints and Graph-specific gotchas, a list of features that are either not supported or not recommended.

- -

JOINS :confounded:

- -
    -
  • They make no sense for Graph, plus Graph hates them! -Which makes them unsupported on purpose. If migrating from an SQL-based app -they will be your boogie monster.
  • -
- -

Pivot Tables in Many-To-Many Relationships

- -

This is not supported, instead we will be using Edges to work with relationships between models.

- -

Nested Arrays and Objects

- -
    -
  • Due to the limitations imposed by the objects map types that can be stored in a single, -you can never have nested arrays or objects in a single model, -make sure it's flat. Example:
  • -
- -

php -// Don't -User::create(['name' => 'Some Name', 'location' => ['lat' => 123, 'lng'=> -123 ] ]); -

- -

Check out the createWith() method on how you can achieve this in a Graph way.

- -

Tests

- -
    -
  • install a Neo4j instance and run it with the default configuration localhost:7474
  • -
  • make sure the database graph is empty to avoid conflicts
  • -
  • after running composer install there should be /vendor/bin/phpunit
  • -
  • run ./vendor/bin/phpunit after making sure that the Neo4j instance is running
  • -
- -
-

Tests marked as incomplete means they are either known issues or non-supported features, -check included messages for more info.

-
From dfafb48fe65c6f7b6eaf7a6e91e1e47fe274f348 Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Fri, 30 Jan 2015 10:33:37 +0200 Subject: [PATCH 4/9] Didn't mean to check this in either --- README.md.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md.html b/README.md.html index ebcca549..651cda1e 100644 --- a/README.md.html +++ b/README.md.html @@ -310,7 +310,7 @@

} -

This represents an OUTGOING relationship between a :User node and another :User.

+

This represents an INCOMING relationship between a :User node and another :User.

$jd = User::find(1012);
 $mc = User::find(1013);

From da4f1d7399996915befc6ed8a5165335900d4177 Mon Sep 17 00:00:00 2001
From: Brian Underwood 
Date: Fri, 30 Jan 2015 10:34:34 +0200
Subject: [PATCH 5/9] Actually removing

---
 README.md.html | 977 -------------------------------------------------
 1 file changed, 977 deletions(-)
 delete mode 100644 README.md.html

diff --git a/README.md.html b/README.md.html
deleted file mode 100644
index 651cda1e..00000000
--- a/README.md.html
+++ /dev/null
@@ -1,977 +0,0 @@
-    
-      
-      
-    
-    
-      
-

SensioLabsInsight

- -

Build Status

- -

-NeoEloquent

- -

Neo4j Graph Eloquent Driver for Laravel 4

- -

-Quick Reference

- - - -

-Installation

- -

Add the package to your composer.json and run composer update.

- -
{
-    "require": {
-        "vinelab/neoeloquent": "*"
-    }
-}
-
- -

Add the service provider in app/config/app.php:

- -
'Vinelab\NeoEloquent\NeoEloquentServiceProvider',
-
- -

The service provider will register all the required classes for this package and will also alias -the Model class to NeoEloquent so you can simply extend NeoEloquent in your models.

- -

-Configuration

- -

in app/config/database.php or in case of an environment-based configuration app/config/[env]/database.php -make neo4j your default connection:

- -
'default' => 'neo4j',
-
- -

Add the connection defaults:

- -
'connections' => [
-    'neo4j' => [
-        'driver' => 'neo4j',
-        'host'   => 'localhost',
-        'port'   => '7474',
-        'username' => null,
-        'password' => null
-    ]
-]
-
- -

-Documentation

- -

-Models

- - - -
class User extends NeoEloquent {}
-
- -

As simple as it is, NeoEloquent will generate the default node label from the class name, -in this case it will be :User. Read about node labels here

- -

-Namespaced Models

- -

When you use namespaces with your models the label will consider the full namespace.

- -
namespace Vinelab\Cms;
-
-class Admin extends NeoEloquent { }
-
- -

The generated label from that relationship will be VinelabCmsAdmin, this is necessary to make sure -that labels do not clash in cases where we introduce another Admin instance like -Vinelab\Blog\Admin then things gets messy with :Admin in the database.

- -

-Custom Node Labels

- -

You may specify the label(s) you wish to be used instead of the default generated, they are also -case sensitive so they will be stored as put here.

- -
class User extends NeoEloquent {
-
-    protected $label = 'User'; // or array('User', 'Fan')
-
-    protected $fillable = ['name', 'email'];
-}
-
-$user = User::create(['name' => 'Some Name', 'email' => 'some@email.com']);
-
- -

NeoEloquent has a fallback support for the $table variable that will be used if found and there was no $label defined on the model.

- -
class User extends NeoEloquent {
-
-    protected $table = 'User';
-
-}
-
- -

Do not worry about the labels formatting, You may specify them as array('Label1', 'Label2') or separate them by a column : and prepending them with a : is optional.

- -

-Soft Deleting

- -

To enable soft deleting you'll need to use Vinelab\NeoEloquent\Eloquent\SoftDeletingTrait -instead of Illuminate\Database\Eloquent\SoftDeletingTrait and just like Eloquent you'll need the $dates in your models as follows:

- -
use Vinelab\NeoEloquent\Eloquent\SoftDeletingTrait;
-
-class User extends NeoEloquent {
-
-    use SoftDeletingTrait;
-
-    protected $dates = ['deleted_at'];
-
-}
-
- -

-Relationships

- - - -

Let's go through some examples of relationships between Nodes.

- -

-One-To-One

- -
class User extends NeoEloquent {
-
-    public function phone()
-    {
-        return $this->hasOne('Phone');
-    }
-
- -

This represents an OUTGOING relationship direction from the :User node to a :Phone.

- -
-Saving
- -
$phone = new Phone(['code' => 961, 'number' => '98765432'])
-$relation = $user->phone()->save($phone);
-
- -

The Cypher performed by this statement will be as follows:

- -
MATCH (user:`User`)
-WHERE id(user) = 1
-CREATE (user)-[:PHONE]->(phone:`Phone` {code: 961, number: '98765432', created_at: 7543788, updated_at: 7543788})
-RETURN phone;
-
- -
-Defining The Inverse Of This Relation
- -
class Phone extends NeoEloquent {
-
-    public function user()
-    {
-        return $this->belongsTo('User');
-    }
-}
-
- -

This represents an INCOMING relationship direction from -the :User node to this :Phone node.

- -
-Associating Models
- -

Due to the fact that we do not deal with foreign keys, in our case it is much -more than just setting the foreign key attribute on the parent model. In Neo4j (and Graph in general) a relationship is an entity itself that can also have attributes of its own, hence the introduction of -Edges

- -
-

Note: Associated models does not persist relations automatically when calling associate().

-
- -
$account = Account::find(1986);
-
-// $relation will be Vinelab\NeoEloquent\Eloquent\Edges\EdgeIn
-$relation = $user->account()->associate($account);
-
-// Save the relation
-$relation->save();
-
- -

The Cypher performed by this statement will be as follows:

- -
MATCH (account:`Account`), (user:`User`)
-WHERE id(account) = 1986 AND id(user) = 9862
-MERGE (account)<-[rel_user_account:ACCOUNT]-(user)
-RETURN rel_user_account;
-
- -

-One-To-Many

- -
class User extends NeoEloquent {
-
-    public function posts()
-    {
-        return $this->hasMany('Post', 'POSTED');
-    }
-}
-
- -

This represents an OUTGOING relationship direction -from the :User node to the :Post node.

- -
$user = User::find(1);
-$post = new Post(['title' => 'The Title', 'body' => 'Hot Body']);
-$user->posts()->save($post);
-
- -

Similar to One-To-One relationships the returned value from a save() statement is an -Edge[In|Out]

- -

The Cypher performed by this statement will be as follows:

- -
MATCH (user:`User`)
-WHERE id(user) = 1
-CREATE (user)-[rel_user_post:POSTED]->(post:`Post` {title: 'The Title', body: 'Hot Body', created_at: '15-05-2014', updated_at: '15-05-2014'})
-RETURN rel_user_post;
-
- -
-Defining The Inverse Of This Relation
- -
class Post extends NeoEloquent {
-
-    public function author()
-    {
-        return $this->belongsTo('User', 'POSTED');
-    }
-}
-
- -

This represents an INCOMING relationship direction from -the :User node to this :Post node.

- -

-Many-To-Many

- -
class User extends NeoEloquent {
-
-    public function followers()
-    {
-        return $this->belongsToMany('User', 'FOLLOWS');
-    }
-}
-
- -

This represents an INCOMING relationship between a :User node and another :User.

- -
$jd = User::find(1012);
-$mc = User::find(1013);
-
- -

$jd follows $mc:

- -
$jd->followers()->save($mc);
-
- -

Or using the attach() method:

- -
$jd->followers()->attach($mc);
-// Or..
-$jd->followers()->attach(1); // 1 being the id of $mc ($mc->getKey())
-
- -

The Cypher performed by this statement will be as follows:

- -
MATCH (user:`User`), (followers:`User`)
-WHERE id(user) = 1012 AND id(followers) = 1013
-CREATE (user)-[:FOLLOWS]->(followers)
-RETURN rel_follows;
-
- -

$mc follows $jd back:

- -
$mc->followers()->save($jd);
-
- -

The Cypher performed by this statement will be as follows:

- -
MATCH (user:`User`), (followers:`User`)
-WHERE id(user) = 1013 AND id(followers) = 1012
-CREATE (user)-[rel_user_followers:FOLLOWS]->(followers)
-RETURN rel_follows;
-
- -

get the followers of $jd

- -
$followers = $jd->followers;
-
- -

The Cypher performed by this statement will be as follows:

- -
MATCH (user:`User`), (followers:`User`), (user)-[rel_user_followers:FOLLOWS]-(followers)
-WHERE id(user) = 1012
-RETURN rel_follows;
-
- -

-Dynamic Properties

- -
class Phone extends Eloquent {
-
-    public function user()
-    {
-        return $this->belongsTo('User');
-    }
-
-}
-
-$phone = Phone::find(1006);
-$user = $phone->user;
-// or getting an attribute out of the related model
-$name = $phone->user->name;
-
- -

-Polymorphic

- -

The concept behind Polymocrphic relations is purely relational to the bone but when it comes -to graph we are representing it as a HyperEdge.

- -

Hyper edges involves three models, the parent model, hyper model and related model -represented in the following figure:

- -

HyperEdges

- -

Similarly in code this will be represented by three models User Comment and Post -where a User with id 1 posts a Post and a User with id 6 COMMENTED a Comment ON that Post -as follows:

- -
class User extends NeoEloquent {
-
-    public function comments($morph = null)
-    {
-        return $this->hyperMorph($morph, 'Comment', 'COMMENTED', 'ON');
-    }
-
-}
-
- -

In order to keep things simple but still involving the three models we will have to pass the -$morph which is any commentable model, in our case it's either a Video or a Post model.

- -
-

Note: Make sure to have it defaulting to null so that we can Dynamicly or Eager load -with $user->comments later on.

-
- -

Creating a Comment with the create() method.

- -
$user = User::find(6);
-$post = Post::find(2);
-
-$user->comments($post)->create(['text' => 'Totally agree!', 'likes' => 0, 'abuse' => 0]);
-
- -

As usual we will have returned an Edge, but this time it's not directed it is an instance of -HyperEdge, read more about HyperEdges here.

- -

Or you may save a Comment instance:

- -
$comment = new Comment(['text' => 'Magnificent', 'likes' => 0, 'abuse' => 0]);
-
-$user->comments($post)->save($comment);
-
- -

Also all the functionalities found in a BelongsToMany relationship are supported like -attaching models by Ids:

- -
$user->comments($post)->attach([$id, $otherId]);
-
- -

Or detaching models:

- -
$user->comments($post)->detach($comment); // or $comment->id
-
- -

Sync too:

- -
$user->comments($post)->sync([$id, $otherId, $someId]);
-
- -

-Retrieving Polymorphic Relations

- -

From our previous example we will use the Video model to retrieve their comments:

- -
class Video extends NeoEloquent {
-
-    public function comments()
-    {
-        return $this->morphMany('Comment', 'ON');
-    }
-
-}
-
- -
-Dynamicly Loading Morph Model
- -
$video = Video::find(3);
-$comments = $video->comments;
-
- -
-Eager Loading Morph Model
- -
$video = Video::with('comments')->find(3);
-foreach ($video->comments as $comment)
-{
-    //
-}
-
- -

-Retrieving The Inverse of a Polymorphic Relation

- -
class Comment extends NeoEloquent {
-
-    public function commentable()
-    {
-        return $this->morphTo();
-    }
-
-}
-
- -
$postComment = Comment::find(7);
-$post = $comment->commentable;
-
-$videoComment = Comment::find(5);
-$video = $comment->commentable;
-
-// You can also eager load them
-Comment::with('commentable')->get();
-
- -

You may also specify the type of morph you would like returned:

- -
class Comment extends NeoEloquent {
-
-    public function post()
-    {
-        return $this->morphTo('Post', 'ON');
-    }
-
-    public function video()
-    {
-        return $this->morphTo('Video', 'ON');
-    }
-
-}
-
- -

-Polymorphic Relations In Short

- -

To drill things down here's how our three models involved in a Polymorphic relationship connect:

- -
class User extends NeoEloquent {
-
-    public function comments($morph = null)
-    {
-        return $this->hyperMorph($morph, 'Comment', 'COMMENTED', 'ON');
-    }
-
-}
-
- -
class Post extends NeoEloquent { // Video is the same as this one
-
-    public function comments()
-    {
-        return $this->morphMany('Comment', 'ON');
-    }
-
-}
-
- -
class Comment extends NeoEloquent {
-
-    public function commentable()
-    {
-        return $this->morphTo();
-    }
-
-}
-
-
- -

-Eager Loading

- -
class Book extends Eloquent {
-
-    public function author()
-    {
-        return $this->belongsTo('Author');
-    }
-}
-
- -

Loading authors with their books with the least performance overhead possible.

- -
foreach (Book::with('author')->get() as $book)
-{
-    echo $book->author->name;
-}
-
- -

Only two Cypher queries will be run in the loop above:

- -
MATCH (book:`Book`) RETURN *;
-
-MATCH (book:`Book`), (book)<-[:WROTE]-(author:`Author`) WHERE id(book) IN [1, 2, 3, 4, 5, ...] RETURN book, author;
-
- -

-Edges

- - - -

-Introduction

- -

Due to the fact that relationships in Graph are much different than other database types so -we will have to handle them accordingly. Relationships have directions that can vary between -In and Out respectively towards the parent node.

- -

-EdgeIn

- -

Represents an INCOMING direction relationship from the related model towards the parent model.

- -
class Location extends NeoEloquent {
-
-    public function user()
-    {
-        return $this->belongsTo('User', 'LOCATED_AT');
-    }
-
-}
-
- -

To associate a User to a Location:

- -
$location = Location::find(1922);
-$user = User::find(3876);
-$relation = $location->associate($user);
-
- -

which in Cypher land will map to (:Location)<-[:LOCATED_AT]-(:User) and $relation -being an instance of EdgeIn representing an incoming relationship towards the parent.

- -

And you can still access the models from the edge:

- -
$relation = $location->associate($user);
-$location = $relation->parent();
-$user = $relation->related();
-
- -

-EdgeOut

- -

Represents an OUTGOING direction relationship from the parent model to the related model.

- -
class User extends NeoEloquent {
-
-    public function posts()
-    {
-        return $this->hasMany('Post', 'POSTED');
-    }
-
-}
-
- -

To save an outgoing edge from :User to :Post it goes like:

- -
$post = new Post(['...']);
-$posted = $user->posts()->save($post);
-
- -

Which in Cypher would be (:User)-[:POSTED]->(:Post) and $posted being the EdgeOut instance.

- -

And fetch the related models:

- -
$edge = $user->posts()->save($post);
-$user = $edge->parent();
-$post = $edge->related();
-
- -

-HyperEdge

- -

This edge comes as a result of a Polymorphic Relation representing an edge involving -two other edges left and right that can be accessed through the left() and right() methods.

- -

This edge is treated a bit different than the others since it is not a direct relationship -between two models which means it has no specific direction.

- -
$edge = $user->comments($post)->attach($comment);
-// Access the left and right edges
-$left = $edge->left();
-$user = $left->parent();
-$comment = $left->related();
-
-$right = $edge->right();
-$comment = $right->parent();
-$post = $right->related();
-
- -

-Working With Edges

- -

As stated earlier Edges are entities to Graph unlike SQL where they are a matter of a -foreign key having the value of the parent model as an attribute on the belonging model or in -Documents where they are either embeds or ids as references. So we developed them to be light -models which means you can work with them as if you were working with an Eloquent instance - to a certain extent, -except HyperEdges.

- -
// Create a new relationship
-$relation = $location->associate($user); // Vinelab\NeoEloquent\Eloquent\Edges\EdgeIn
-
-// Save the relationship to the database
-$relation->save(); // true
-
- -

In the case of a HyperEdge you can access all three models as follows:

- -
$edge    = $user->comments($post)->save($comment);
-$user    = $edge->parent();
-$comment = $edge->hyper();
-$post    = $edge->related();
-
- -

-Edge Attributes

- -

By default, edges will have the timestamps created_at and updated_at automatically set and updated only if timestamps are enabled by setting $timestamps to true -on the parent model.

- -
$located_at = $location->associate($user);
-$located_at->since = 1966;
-$located_at->present = true;
-$located_at->save();
-
-// $created_at and $updated_at are Carbon\Carbon instances
-$created_at = $located_at->created_at;
-$updated_at = $located_at->updated_at;
-
- -
-Retrieve an Edge from a Relation
- -

The same way an association will create an EdgeIn relationship we can retrieve -the edge between two models by calling the edge($model) method on the belongsTo -relationship.

- -
$location = Location::find(1892);
-$edge = $location->user()->edge();
-
- -

You may also specify the model at the other side of the edge.

- -
-

Note: By default NeoEloquent will try to pefrorm the $location->user internally to figure -out the related side of the edge based on the relation function name, in this case it's -user().

-
- -
$location = Location::find(1892);
-$edge = $location->user()->edge($location->user);
-
- -

-Only in Neo

- - - -

Here you will find NeoEloquent-specific methods and implementations that with the -wonderful Eloquent methods would make working with Graph and Neo4j a blast!

- -

-CreateWith

- - - -

This method will "kind of" fill the gap between relational and document databases, -it allows the creation of multiple related models with one database hit.

- -

-Creating New Records and Relations

- -

Here's an example of creating a post with attached photos and videos:

- -
class Post extends NeoEloquent {
-
-    public function photos()
-    {
-        return $this->hasMany('Photo', 'PHOTO');
-    }
-
-    public function videos()
-    {
-        return $this->hasMany('Video', 'VIDEO');
-    }
-}
-
- -

-Post::createWith(['title' => 'the title', 'body' => 'the body'], [
-    'photos' => [
-        [
-            'url'      => 'http://url',
-            'caption'  => '...',
-            'metadata' => '...'
-        ],
-        [
-            'url' => 'http://other.url',
-            'caption' => 'the bay',
-            'metadata' => '...'
-        ]
-    ],
-
-    'videos' => [
-        'title' => 'Boats passing us by',
-        'description' => '...'
-    ]
-]);
-
- -
-

The keys photos and videos must be the same as the relation method names in the -Post model.

-
- -

The Cypher query performed by the example above is:

- -
CREATE (post:`Post` {title: 'the title', body: 'the body'}),
-(post)-[:PHOTO]->(:`Photo` {url: 'http://url', caption: '...', metadata: '...'}),
-(post)-[:PHOTO]->(:`Photo` {url: 'http://other', caption: 'the bay', metadata: '...'}),
-(post)-[:VIDEO]->(:`Video` {title: 'Boats passing us by', description: '...'});
-
- -

We will get the nodes created with their relations as such:

- -

CreateWith

- -

You may also mix models and attributes as relation values but it is not necessary -since NeoEloquent will pass the provided attributes through the $fillable -filter pipeline:

- -
$videos = new Video(['title' => 'foo', 'description' => 'bar']);
-Post::createWith($info, compact('videos'));
-
- -

You may also use a single array of attributes as such:

- -
class User extends NeoEloquent {
-
-    public function account()
-    {
-        return $this->hasOne('Account');
-    }
-}
-
-User::createWith(['name' => 'foo'], ['account' => ['guid' => 'bar', 'email' => 'some@mail.net']]);
-
- -

-Attaching Existing Records as Relations

- -

createWith is intelligent enough to know the difference when you pass an existing model, -a model Id or new records that you need to create which allows mixing new records with existing ones.

- -
class Post extends NeoEloquent {
-
-    public function tags()
-    {
-        return $this->hasMany('Tag', 'TAG');
-    }
-}
-
- -
$tag1 = Tag::create(['title' => 'php']);
-$tag2 = Tag::create(['title' => 'dev']);
-
-$post = Post::createWith(['title' => 'foo', 'body' => 'bar'], ['tags' => [$tag1, $tag2]]);
-
- -

And we will get the Post related to the existing Tag nodes.

- -

Or using the id of the model:

- -
Post::createWith(['title' => 'foo', 'body' => 'bar'], ['tags' => 1, 'privacy' => 2]);
-
- -

The Cypher for the query that attaches records would be:

- -
CREATE (post:`Post` {title: 'foo', 'body' => 'bar'})
-WITH post
-MATCH (tag:`Tag`)
-WHERE id(tag) IN [1, 2]
-CREATE (post)-[:TAG]->(tag);
-
- -

-Aggregates

- -

In addition to the Eloquent builder aggregates, NeoEloquent also has support for -Neo4j specific aggregates like percentile and standard deviation, keeping the same -function names for convenience. -Check the docs for more.

- -
-

table() represents the label of the model

-
- -
$users = DB::table('User')->count();
-
-$distinct = DB::table('User')->countDistinct('points');
-
-$price = DB::table('Order')->max('price');
-
-$price = DB::table('Order')->min('price');
-
-$price = DB::table('Order')->avg('price');
-
-$total = DB::table('User')->sum('votes');
-
-$disc = DB::table('User')->percentileDisc('votes', 0.2);
-
-$cont = DB::table('User')->percentileCont('votes', 0.8);
-
-$deviation = DB::table('User')->stdev('sex');
-
-$population = DB::table('User')->stdevp('sex');
-
-$emails = DB::table('User')->collect('email');
-
- -

-Changelog

- -

Check the Releases for details.

- -

-Avoid

- -

Here are some constraints and Graph-specific gotchas, a list of features that are either not supported or not recommended.

- -

-JOINS :confounded: -

- -
    -
  • They make no sense for Graph, plus Graph hates them! -Which makes them unsupported on purpose. If migrating from an SQL-based app -they will be your boogie monster.
  • -
- -

-Pivot Tables in Many-To-Many Relationships

- -

This is not supported, instead we will be using Edges to work with relationships between models.

- -

-Nested Arrays and Objects

- -
    -
  • Due to the limitations imposed by the objects map types that can be stored in a single, -you can never have nested arrays or objects in a single model, -make sure it's flat. Example: -
  • -
- -
// Don't
-User::create(['name' => 'Some Name', 'location' => ['lat' => 123, 'lng'=> -123 ] ]);
-
- -

Check out the createWith() method on how you can achieve this in a Graph way.

- -

-Tests

- -
    -
  • install a Neo4j instance and run it with the default configuration localhost:7474 -
  • -
  • make sure the database graph is empty to avoid conflicts
  • -
  • after running composer install there should be /vendor/bin/phpunit -
  • -
  • run ./vendor/bin/phpunit after making sure that the Neo4j instance is running
  • -
- -
-

Tests marked as incomplete means they are either known issues or non-supported features, -check included messages for more info.

-
-
- From a15d4222da5dc8f08d02a7c98df04b3f0ffcb5cc Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Fri, 30 Jan 2015 10:35:48 +0200 Subject: [PATCH 6/9] I think that belongsToMany means that this is going to be an incoming relationship --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1d35e7c..ed3e2425 100644 --- a/README.md +++ b/README.md @@ -271,7 +271,7 @@ class User extends NeoEloquent { } ``` -This represents an `OUTGOING` relationship between a `:User` node and another `:User`. +This represents an `INCOMING` relationship between a `:User` node and another `:User`. ```php $jd = User::find(1012); From 71be948976fd689f97b83fa7b2ea12e91b74bf6c Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Fri, 30 Jan 2015 10:39:30 +0200 Subject: [PATCH 7/9] Should use the ID that was specified for $mc above. Also, changing around the cypher (`followers` should be `follower`, and the follower follows the user) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ed3e2425..f57ed224 100644 --- a/README.md +++ b/README.md @@ -289,15 +289,15 @@ Or using the `attach()` method: ```php $jd->followers()->attach($mc); // Or.. -$jd->followers()->attach(1); // 1 being the id of $mc ($mc->getKey()) +$jd->followers()->attach(1013); // 1013 being the id of $mc ($mc->getKey()) ``` The Cypher performed by this statement will be as follows: ``` -MATCH (user:`User`), (followers:`User`) -WHERE id(user) = 1012 AND id(followers) = 1013 -CREATE (user)-[:FOLLOWS]->(followers) +MATCH (user:`User`), (follower:`User`) +WHERE id(user) = 1012 AND id(follower) = 1013 +CREATE (follower)-[:FOLLOWS]->(user) RETURN rel_follows; ``` From 4f9f9fe079e315e385aef7aa7c021d31c1f806bf Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Fri, 30 Jan 2015 10:42:05 +0200 Subject: [PATCH 8/9] I think these should both be `extends NeoEloquent` --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f57ed224..8dca4903 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,7 @@ RETURN rel_follows; ### Dynamic Properties ```php -class Phone extends Eloquent { +class Phone extends NeoEloquent { public function user() { @@ -533,7 +533,7 @@ class Comment extends NeoEloquent { ### Eager Loading ```php -class Book extends Eloquent { +class Book extends NeoEloquent { public function author() { From 8c1bb4b74d735f019234940886a2dfddee0fb280 Mon Sep 17 00:00:00 2001 From: Brian Underwood Date: Fri, 30 Jan 2015 11:48:23 +0200 Subject: [PATCH 9/9] Should remain 'followers' because that's the association name and that's what is generated by NeoEloquent --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8dca4903..43ff253b 100644 --- a/README.md +++ b/README.md @@ -295,9 +295,9 @@ $jd->followers()->attach(1013); // 1013 being the id of $mc ($mc->getKey()) The Cypher performed by this statement will be as follows: ``` -MATCH (user:`User`), (follower:`User`) -WHERE id(user) = 1012 AND id(follower) = 1013 -CREATE (follower)-[:FOLLOWS]->(user) +MATCH (user:`User`), (followers:`User`) +WHERE id(user) = 1012 AND id(followers) = 1013 +CREATE (followers)-[:FOLLOWS]->(user) RETURN rel_follows; ```