Neo4j Graph Eloquent Driver for Laravel 5.

Overview

Build Status

NeoEloquent

Neo4j Graph Eloquent Driver for Laravel 5.

Quick Reference

Installation

Run composer require vinelab/neoeloquent

Or add the package to your composer.json and run composer update.

{
    "require": {
        "vinelab/neoeloquent": "1.8.*"
    }
}

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

Connection

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
    ]
]

Migration Setup

If you're willing to have migrations:

  • create the folder app/database/labels
  • modify composer.json and add app/database/labels to the classmap array
  • run composer dump-autoload

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' => '[email protected]']);

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(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 (followers)-[:FOLLOWS]->(user)
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 NeoEloquent {

    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 Polymorphic 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 NeoEloquent {

    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.

Edges give you the ability to manipulate relationships properties the same way you do with models.

$edge = $location->associate($user);
$edge->last_visited = 'today';
$edge->save(); // true

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' => '[email protected]']]);

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);

Migration

For migrations to work please perform the following:

  • create the folder app/database/labels
  • modify composer.json and add app/database/labels to the classmap array

Since Neo4j is a schema-less database you don't need to predefine types of properties for labels. However you will be able to perform Indexing and Constraints using NeoEloquent's pain-less Schema.

Commands

NeoEloquent introduces new commands under the neo4j namespace so you can still use Eloquent's migration commands side-by-side.

Migration commands are the same as those of Eloquent, in the form of neo4j:migrate[:command]

neo4j:make:migration                 Create a new migration file
neo4j:migrate                        Run the database migrations
neo4j:migrate:reset                  Rollback all database migrations
neo4j:migrate:refresh                Reset and re-run all migrations
neo4j:migrate:rollback               Rollback the last database migration

Creating Migrations

Like in Laravel you can create a new migration by using the make command with Artisan:

php artisan neo4j:migrate:make create_user_label

Label migrations will be placed in app/database/labels

You can add additional options to commands like:

php artisan neo4j:migrate:make foo --path=app/labels
php artisan neo4j:migrate:make create_user_label --create=User
php artisan neo4j:migrate:make create_user_label --label=User

Running Migrations

Run All Outstanding Migrations
php artisan neo4j:migrate
Run All Outstanding Migrations For A Path
php artisan neo4j:migrate --path=app/foo/labels
Run All Outstanding Migrations For A Package
php artisan neo4j:migrate --package=vendor/package

Note: If you receive a "class not found" error when running migrations, try running the composer dump-autoload command.

Forcing Migrations In Production

To force-run migrations on a production database you can use:

php artisan neo4j:migrate --force

Rolling Back Migrations

Rollback The Last Migration Operation
php artisan neo4j:migrate:rollback
Rollback all migrations
php artisan neo4j:migrate:reset
Rollback all migrations and run them all again
php artisan neo4j:migrate:refresh

php artisan neo4j:migrate:refresh --seed

Schema

NeoEloquent will alias the Neo4jSchema facade automatically for you to be used in manipulating labels.

Neo4jSchema::label('User', function(Blueprint $label)
{
    $label->unique('uuid');
});

If you decide to write Migration classes manually (not using the generator) make sure to have these use statements in place:

  • use Vinelab\NeoEloquent\Schema\Blueprint;
  • use Vinelab\NeoEloquent\Migrations\Migration;

Currently Neo4j supports UNIQUE constraint and INDEX on properties. You can read more about them at

http://docs.neo4j.org/chunked/stable/graphdb-neo4j-schema.html

Schema Methods

Command Description
$label->unique('email') Adding a unique constraint on a property
$label->dropUnique('email') Dropping a unique constraint from property
$label->index('uuid') Adding index on property
$label->dropIndex('uuid') Dropping index from property

Droping Labels

Neo4jSchema::drop('User');
Neo4jSchema::dropIfExists('User');

Renaming Labels

Neo4jSchema::renameLabel($from, $to);

Checking Label's Existence

if (Neo4jSchema::hasLabel('User')) {

} else {

}

Checking Relation's Existence

if (Neo4jSchema::hasRelation('FRIEND_OF')) {

} else {

}

You can read more about migrations and schema on:

http://laravel.com/docs/schema

http://laravel.com/docs/migrations

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 😖

  • 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.

Factories

You can use default Laravel factory() helper for NeoEloquent models too.

Comments
  • Setting relationships when using custom primaryKey

    Setting relationships when using custom primaryKey

    Hi. I'm using custom primary keys named 'uuid' on my models Author and Book. Each model has protected $primaryKey = 'uuid'; public $incrementing = false; I want to create a relationship from Author to Book, labeled :WRITE

    $book->setAuthor()->associate($author)->save()

    my setAuthor() is this: public function setAuthor(){ return $this->belongsTo('\App\Http\Model\Author', 'WRITE'); }

    and is returning me Exception in GetNodeRelationships.php line 73: No node id specified

    can someone help me?

    opened by guivazcabral 28
  • Problem with pagination

    Problem with pagination

    when trying this code: dd(User::paginate(2)); in my controller; i'm getting the following exception: ErrorException in CypherGrammar.php line 641: Undefined index: label I'm using NeoEloquent v1.2.5

    Bug 
    opened by adibhanna 22
  • SoftDeleted records being returned when fetched from related models

    SoftDeleted records being returned when fetched from related models

    Note, i'm using v.1.2.5

    After soft deleting a record, the graph shows the deleted_at field with the expected timestamp set. However, the record is still returned. See code below:

    use Vinelab\NeoEloquent\Eloquent\SoftDeletes;
    
    class User extends NeoEloquent {
    
        use SoftDeletes;
    
        protected $dates = ['deleted_at'];
    
        public function cover()
        {
            return $this->hasOne('App\Graph\Photo', 'COVER_PHOTO');
        }
    
    }
    

    After performing a SoftDelete like so:.

    $cover = $user->cover;
    $cover->delete();
    

    $cover = $user->cover still returns the record even though the deleted_at field isset and NOT NULL

    Anyone else come across this?

    Best,

    Bug Wontfix 
    opened by marlongichie 17
  • `create` not included in transactions

    `create` not included in transactions

    I'm using Laravel 5.1 and in our tests we make use of database transactions to make sure all tests start with a clean slate.

    The application has 2 databases: Mysql and neo4j. In one of our tests I'm using Label::create() to create a node.

    We've setup our setUp() like this:

    $db = $this->app->make('db');
    $db->beginTransaction();
    $db->connection('neo4j')->beginTransaction();
    
    $this->beforeApplicationDestroyed(function () use ($db) {
         $db->connection('neo4j')->beginTransaction();
         $db->rollback();
    });
    

    However, after the tests are finished the nodes still exist in the database.

    I think the reason for this is that the create call doesn't use the transaction when the model uses incrementing ids. In that case it simply does a node->save() instead of passing through the connection:

    insertGetId vs insert.

    Is there a specific reason the insertGetId does not use the connection instance? And if there isn't, is it possible to make use of it?

    I've also tried to "cheat" by using a createWith() instead, but then I suffer from another bug:

    Unexpected end of input: expected whitespace or org$neo4j$cypher$internal$frontend$v2_3$parser$Clauses$$ReturnItem (line 1, column 169 (offset: 168))
    "CREATE (project:`Project` { reference_id: 'acac5bd7-a6b7-43c7-b946-9ccb88c59232', updated_at: '2015-12-15 16:19:34', created_at: '2015-12-15 16:19:34'}) RETURN project,"
    

    (note the extra , at the end of the query, because I'm passing no relationships).

    Bug Wontfix 
    opened by bramdevries 16
  • [question] Convert ResultSet to Models - DB::query('Cypher') support

    [question] Convert ResultSet to Models - DB::query('Cypher') support

    Hi,

    Following to my question #24 I would like to be able to write my own Cypher queries while maintaining Neoloquent models. I'm aware that I could probably achieve the same thing using eloquent style queries, but sometimes Cypher is just more expressive to me.

    I managed to run my Cypher query like this.

    $client = DB::connection('neo4j')->getClient();
            $queryString = "START n=node({userId}) MATCH (n)-[:HAS_PERMISSION]->(p:Permission)-[:ON]->(t:Thing) WHERE p.access_level>0 RETURN t";
            $query = new Query($client, $queryString, array('userId'=>Auth::user()->id));
            $result = $query->getResultSet();
    

    It runs great and return me an object of type

    Everyman\Neo4j\Query\ResultSet

    Now I'd like to convert that resulset into "Thing" models. I've seen that in

    Vinelab\NeoEloquent\Eloquent\Builder

    There is a protected method "resultToModels"

    /**
         * Turn Neo4j result set into the corresponding model
         * @param  string $connection
         * @param  \Everyman\Neo4j\Query\ResultSet $results
         * @return array
         */
        protected function resultsToModels($connection, ResultSet $results)
        {
            $models = [];
    
            if ($results->valid())
            {
                $columns = $results->getColumns();
    
                foreach ($results as $result)
                {
                    $attributes = $this->getProperties($columns, $result);
    
                    // Now that we have the attributes, we first check for mutations
                    // and if exists, we will need to mutate the attributes accordingly.
                    if ($this->shouldMutate($attributes))
                    {
                        $models[] = $this->mutateToOrigin($result, $attributes);
                    }
                    // This is a regular record that we should deal with the normal way, creating an instance
                    // of the model out of the fetched attributes.
                    else
                    {
                        $model = $this->model->newFromBuilder($attributes);
                        $model->setConnection($connection);
                        $models[] = $model;
                    }
                }
            }
    
            return $models;
        }
    

    It seems like the good fit for what I'm trying to achieve. But beyond that point I began to feel a bit lost in the code :-(

    What do you think ?

    Wontfix Feature Discussion 
    opened by thylo 14
  • Laravel 6.0 Support

    Laravel 6.0 Support

    Is there a plan to add support for the newest version of Laravel? The currently supported versions are getting to the end of their support period, so having the option to upgrade while using NeoEloquent would be great.

    Wontfix 
    opened by oliverwhite19 13
  • Multiple where clauses not working

    Multiple where clauses not working

    I was tracking down and issue and found that multiple where clauses do not work as expected. For example:

    $patient = $patient->where('last_name','=~','(?i)Wolff.*')->orWhere('last_name','=~','(?i)Koch.*');
    

    When I run this, it returns two patients with a last name of Koch. If I then manually switch the query:

    $patient = $patient->where('last_name','=~','(?i)Koch.*')->orWhere('last_name','=~','(?i)Wolff.*');
    

    I then get a patient with a last name of Wolff.

    I am trying to get the code to return all patients named Koch or Wolff, but it seems like the last where() clause provided wipes out the previous ones.

    Bug 
    opened by PadreMontoya 13
  • Get all label names of a node

    Get all label names of a node

    Hey, it would be extremely useful if there was a function for checking out all the labels for a given node. For now using $myModelInstance->getTable() or $myModelInstance->getDefaultNodeLabel() would only return the first label for that node.

    UPDATE:

    It turned out this is specific to a case I have, occurring for nodes bearing multiple labels. Here is a detailed example.

    I have two labels: Startup, Organization and Profile. I have the following node classes:

    class Startup extends NeoEloquent { protected $label = ['Startup', 'Profile']; }

    class Organization extends NeoEloquent { protected $label = ['Organization', 'Profile']; }

    class Startup extends NeoEloquent { protected $label = ['Profile']; }

    There could be many other node classes having Profile as their label too. What I am trying to do is query the Profile model by a common attribute between the other profiles, get the result and infer the type of node from that. Example: Profile::where('slug','=','something')->first();

    The above example would query all nodes that bear the label Profile regardless of the rest of their labels (which is exactly what I want) but of course the object type of the returned result is Profile. Calling getTable() on the result would simply return ['Profile'] despite the match being on a node that has multiple labels such as Startup or Organization.

    Let me know if my idea is clear enough, would love to hear your thoughts about this. In case it can be a possible feature or there is a good way I can go around this issue.

    Wontfix Feature 
    opened by aymanfarhat 12
  • Problem when run: php artisan neo4j:migrate

    Problem when run: php artisan neo4j:migrate

    [ErrorException]
    Declaration of Vinelab\NeoEloquent\Eloquent\Builder::simplePaginate() should be compatible with Illuminate\Database\Eloquent\Builder::simplePaginate($perPage = NULL, $columns = Array, $pageName = 'page')

    Wontfix 
    opened by divanski 11
  • Can i do this? `with` with a `where` condition?

    Can i do this? `with` with a `where` condition?

    On Laravel 5.3 and the appropriate NeoEloquent.

    I'd like to run the following query:

    Card::with(['owner','coupons' => function($q) {
                $q->where('status', '!=', 'used')->where('expiry_date', '>', Carbon::now());
            }])->find($id);
    

    It's not working right. I do have coupons in the DB which qualify the inner conditions. Is there a way to do this in NeoEloquent. As this is possible in basic #Laravel.

    If not, then i guess this would be a feature request.

    Question 
    opened by oberoivarun 10
  • QueryException when using orderBy

    QueryException when using orderBy

    Hi, in Laravel 5.5 when i use orderBy in my query i get Query Exception:

    SyntaxException: Variable advertise not defined (line 1, column 56 (offset: 55)) "MATCH (advertise:Advertise) RETURN count(*) ORDER BY advertise.created_at DESC"

    this because in query advertise not returned and if return advertise everything is ok: "MATCH (advertise:Advertise) RETURN advertise, count(*) ORDER BY advertise.created_at DESC"

    but i dont know how to correct this problem, please help me, thanks.

    Bug Wontfix Compatibility 
    opened by yavand 10
  • edges

    edges

    i have this relation inside CompanyM node public function work() { return $this->hasMany('App\Models\CompanyM', 'WorkedAt'); }

    and i am trying to get the edge like this $w = $items->work()->edges($w);

    w is a CompanyM node

    but i got an empty value

    any help?

    thanks

    opened by ENGAHMAD97 0
  •   Command

    Command "neo4j:migrate:make" is not defined.

    hi, as the docs, the command php artisan neo4j:migrate:make ..... will return back this error Command "neo4j:migrate:make" is not defined.

    opened by devmrh 1
  • MigrateMakeCommand::__invoke() does not exist error

    MigrateMakeCommand::__invoke() does not exist error

    hi, when im using this command php artisan neo4j:make:migration create_coin_label --label=Coin

    i get this error: (laravel 8 & php 8.1)


    ReflectionException 
    
      Method Vinelab\NeoEloquent\Console\Migrations\MigrateMakeCommand::__invoke() does not exist
    
      at vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:147
        143▕             $callback = [$callback, '__invoke'];
        144▕         }
        145▕ 
        146▕         return is_array($callback)
      ➜ 147▕                         ? new ReflectionMethod($callback[0], $callback[1])
        148▕                         : new ReflectionFunction($callback);
        149▕     }
        150▕ 
        151▕     /**
    
          +15 vendor frames 
      16  artisan:37
    
    
    opened by devmrh 1
  • A way of querying based on relationship attributes.

    A way of querying based on relationship attributes.

    Hi. I've checked the source code and documentation for a solution to my issue but was unable to. I am working on an app that allow users to have relationships between them. They can be friends, but also relatives. I'm managing this by having a "type" attribute on the relationship. Is there any way, for example, to query a certain Person A and fetch all of their sons/children, or only their friends?

    Thank you!

    Question 
    opened by xndbogdan 1
Releases(v1.8.2)
Owner
Vinelab
Vinelab
A Laravel package to fetch Open Graph data of a website.

OpenGraph is a laravel package to fetch Open Graph metadata of a website/link. Features Easily fetch metadata of a URL. Laravel OpenGraph fetches all

Shashi Prakash Gautam 128 Dec 24, 2022
kreatinc project (laravel graph Facebook api)

kreatinc project (laravel graph Facebook api)

Useef 4 Nov 4, 2022
Google Cloud Storage filesystem driver for Laravel

Google Cloud Storage filesystem driver for Laravel Google Cloud Storage filesystem driver for Laravel. This started as a fork from Superbalist/laravel

Spatie 88 Dec 16, 2022
A Laravel Broadcast Driver for Centrifugo

Laravel Centrifugo Features Compatible with latest Centrifugo 3.0.3 Contains instructions and configuration file for setting up with Laravel Sail Requ

null 7 Jun 29, 2022
Driver for Laravel Scout search package based on tntsearch

Driver for Laravel Scout search package based on https://github.com/teamtnt/tntsearch

TNT Studio 1k Dec 27, 2022
SingleStore DB Driver for Laravel

SingleStore DB Driver for Laravel This package provides SingleStore specific schema options, currently supporting keys & shard keys, alongside setting

Charlie Joseph 16 Oct 18, 2022
Driver for managing cash payments in the Cashier Provider ecosystem

Cash Driver Provider Installation To get the latest version of Cash Driver Provider, simply require the project using Composer: $ composer require cas

Cashier Provider 4 Aug 30, 2022
Laravel comments - This package enables to easily associate comments to any Eloquent model in your Laravel application

Laravel comments - This package enables to easily associate comments to any Eloquent model in your Laravel application

Rubik 4 May 12, 2022
This Laravel package merges staudenmeir/eloquent-param-limit-fix and staudenmeir/laravel-adjacency-list to allow them being used in the same model.

This Laravel package merges staudenmeir/eloquent-param-limit-fix and staudenmeir/laravel-adjacency-list to allow them being used in the same model.

Jonas Staudenmeir 5 Jan 6, 2023
An Eloquent Way To Filter Laravel Models And Their Relationships

Eloquent Filter An Eloquent way to filter Eloquent Models and their relationships Introduction Lets say we want to return a list of users filtered by

Eric Tucker 1.5k Jan 7, 2023
Easy creation of slugs for your Eloquent models in Laravel

Eloquent-Sluggable Easy creation of slugs for your Eloquent models in Laravel. NOTE: These instructions are for the latest version of Laravel. If you

Colin Viebrock 3.6k Dec 30, 2022
Automatically validating Eloquent models for Laravel

Validating, a validation trait for Laravel Validating is a trait for Laravel Eloquent models which ensures that models meet their validation criteria

Dwight Watson 955 Dec 25, 2022
Laravel Ban simplify blocking and banning Eloquent models.

Laravel Ban Introduction Laravel Ban simplify management of Eloquent model's ban. Make any model bannable in a minutes! Use case is not limited to Use

cybercog 879 Dec 30, 2022
cybercog 996 Dec 28, 2022
Collection of the Laravel/Eloquent Model classes that allows you to get data directly from a Magento 2 database.

Laragento LAravel MAgento Micro services Magento 2 has legacy code based on abandoned Zend Framework 1 with really ugly ORM on top of outdated Zend_DB

Egor Shitikov 87 Nov 26, 2022
The missing laravel helper that allows you to inspect your eloquent queries with it's bind parameters

Laravel Query Inspector The missing laravel helper that allows you to ispect your eloquent queries with it's bind parameters Motivations Let's say you

Mouad ZIANI 59 Sep 25, 2022
Laravel Quran is static Eloquent model for Quran.

Laravel Quran بِسْمِ ٱللّٰهِ الرَّحْمٰنِ الرَّحِيْمِ Laravel Quran is static Eloquent model for Quran. The Quran has never changed and never will, bec

Devtical 13 Aug 17, 2022
A package for Laravel One Time Password (OTP) generator and validation without Eloquent Model, since it done by Cache.

Laravel OTP Introduction A package for Laravel One Time Password (OTP) generator and validation without Eloquent Model, since it done by Cache. The ca

Lim Teck Wei 52 Sep 6, 2022
Provides a Eloquent query builder for Laravel or Lumen

This package provides an advanced filter for Laravel or Lumen model based on incoming requets.

M.Fouladgar 484 Jan 4, 2023