GithubHelp home page GithubHelp logo

neoeloquent's Introduction

Build Status

NeoEloquent

Neo4j Graph Eloquent Driver for Laravel.

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::class,

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'   => 7687,
        'username' => 'username',
        'password' => 'password',
    ]
]

Remember to update your ENV variables (.env) in case you're using them.

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

use NeoEloquent;

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 App\Models;

use NeoEloquent;

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.

use NeoEloquent;

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.

use NeoEloquent;

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:make:migration create_user_label

Label migrations will be placed in app/database/labels

You can add additional options to commands like:

php artisan neo4j:make:migration foo --path=app/labels
php artisan neo4j:make:migration create_user_label --create=User
php artisan neo4j:make:migration 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.

neoeloquent's People

Contributors

iyhunko avatar kinaned avatar mrbig avatar mulkave avatar transistive avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

neoeloquent's Issues

Odd behavior of createWith with reverse relationships

createWith fed with empty array() in relations part when the relation in question is outgoing (hasOne, hasMany), ignores the argument but for incoming or reverse relations (belongsTo, belongsToMany), it creates an empty related node which is quiet annoying to handle. Especially when the modal being created has many such relations.

Update: It seems, in some cases it ignores making nodes and in some it does. I don't understand.

Laravel 5 support

Hi @Mulkave,

Thanks for great library, It is so useful

I just wanted to know, when do you planning to provide support for laravel 5.

'Where' on multiple relations not working

Hi!
I've found a bug when trying to get nodes with a condition on related nodes.

Here is what I'm doing :

$query = Thing::query();

if(isset($params['author_id']))
{
    $query->whereHas('author', function($q) use ($params){
        $q->where('id', '=', intval($params['author_id']));  //THIS IS WORKING
    });
}

if(isset($params['city_id']))
{
    $query->whereHas('locations', function($q) use ($params){
        $q->whereHas('city', function($q2) use($params){
            $q2->where('id', '=', intval($params['city_id'])); //THIS IS NOT WORKING
        });
    });
}

$count = $query->count();
$list = $query->take($params['count'])->offset($params['start'])->get();

So if I juste have an author_id I get a list of Things, but If I've juste a city_id I get this error :

api.ERROR: exception 'Vinelab\NeoEloquent\QueryException' with message 'SyntaxException: city not defined (line 1, column 207)
"MATCH (thing:`Thing`), (thing)<-[rel_localize_location:LOCALIZE]-(location:`Location`) WHERE thing.deleted_at is null and location.deleted_at is null and location.city.deleted_at is null and id(city) = {idcity} RETURN count(*)" /vagrant/www/myproject/vendor/vinelab/neoeloquent/src/Vinelab/NeoEloquent/Connection.php:424

Stack trace:
#0 /vagrant/www/myproject/vendor/vinelab/neoeloquent/src/Vinelab/NeoEloquent/Connection.php(152): Vinelab\NeoEloquent\Connection->run('MATCH (anecdote...', Array, Object(Closure))
#1 /vagrant/www/myproject/vendor/vinelab/neoeloquent/src/Vinelab/NeoEloquent/Query/Builder.php(452): Vinelab\NeoEloquent\Connection->select('MATCH (anecdote...', Array)
#2 /vagrant/www/myproject/vendor/vinelab/neoeloquent/src/Vinelab/NeoEloquent/Query/Builder.php(442): Vinelab\NeoEloquent\Query\Builder->runSelect()
#3 /vagrant/www/myproject/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(1336): Vinelab\NeoEloquent\Query\Builder->getFresh(Array)
#4 /vagrant/www/myproject/vendor/vinelab/neoeloquent/src/Vinelab/NeoEloquent/Query/Builder.php(619): Illuminate\Database\Query\Builder->get(Array)
#5 /vagrant/www/myproject/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(1713): Vinelab\NeoEloquent\Query\Builder->aggregate('count', Array)
#6 [internal function]: Illuminate\Database\Query\Builder->count()
#7 /vagrant/www/myproject/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(913): call_user_func_array(Array, Array)
#8 /vagrant/www/myproject/app/controllers/api/APIAnecdoteController.php(62): Illuminate\Database\Eloquent\Builder->__call('count', Array)
#9 /vagrant/www/myproject/app/controllers/api/APIAnecdoteController.php(62): Vinelab\NeoEloquent\Eloquent\Builder->count()

Wrong Cypher SET query gets generated when updating a model after a model event

When trying out model events in a NeoEloquent model, I want to update a node after it gets created by adding a new field (username in this case).

Code for the model:

class StartupProfile extends NeoEloquent {
    protected $label = ['Startup', 'Profile'];
    protected $fillable = [
                'name', 
                'profile_pic', 
                'date_established', 
                'facebook', 
                'twitter', 
                'linkedin', 
                'brief_description',
                'address',
                'operating_status',
                'website',
                'username'
    ];

    public static function boot()
    {
        StartupProfile::created(function($startup){
            $startup->username='testing';
            $startup->save();
        });
    }
}

When trying to save the instance of the model to set the new value, it crashes with a syntax exception from the cypher query. Looking a bit into it, I noticed that it is also setting the id of the node via id(startup) = {id_update} which as far as I know is not allowed in the SET syntax in cypher.

Here is the full output:

  [Vinelab\NeoEloquent\QueryException]
  SyntaxException: Invalid input '=': expected whitespace, comment or '.' (line 1, column 501)
  "MATCH (startup:`Startup`:`Profile`) WHERE id(startup) = {idstartup} SET startup.name = {name_update}, startup.website = {website_update}, startup.facebook = {facebook_updat
  e}, startup.linkedin = {linkedin_update}, startup.twitter = {twitter_update}, startup.brief_description = {brief_description_update}, startup.profile_pic = {profile_pic_upda
  te}, startup.date_established = {date_established_update}, startup.updated_at = {updated_at_update}, startup.created_at = {created_at_update}, id(startup) = {id_update}, sta
  rtup.username = {username_update} RETURN count(startup)"

Thanks!

Can I get executable query

I want to check The Cypher performed query by this statement.

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

For this statement following Cypher query will get generated
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;

How can I get Cypher query performed by the statement.

Thanks

Where conditions on related entities, case sensitivity issue on the alias in query

Hey guys, here is an issue I am facing when trying to use where to filter out a relation with another entity.

For example:

I have the following relation:

public function investmentRounds()
{
     return $this->hasMany('InvestmentRound', 'RECEIVED');
}
$round = $startup->investmentRounds()
                 ->where('type', '=', $roundType)
                 ->get();

The respective query that gets generated is:

MATCH (startup:`Startup`:`Profile`), (startup)-[rel_received_investmentRounds:RECE
  IVED]->(investmentRounds:`InvestmentRound`) WHERE id(startup) = {idstartup} and inv
  estmentrounds.type = {type} and investmentrounds.type = {type_2} RETURN investmentRounds

Which obviously throws an exception, because the alias here is investmentRounds and not investmentrounds

SyntaxException: investmentrounds not defined (line 1, column 163)

So in summary, the alias for the related info is camel case just like the function defining this relationship, but for some reason it gets handled all as lower case in the rest of the query.

Probably the simplest way to just get going for now is to name that function all lowercase. As a quick workaround until the issue is solved I guess...

Thanks!

Case Insensitive where conditions

Suppose I need to get user information according to username

How can I execute following query from NeoEloqunt
Example: MATCH (u:user) where u.username = '(?i)satish';

Need to have where condition case Insensitive.

Or is there any functionality like DB::raw

Please help me on this.

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.

Login isn't working with NeoEloquent

I integrated NeoEloquent with my laravel project as instructed and wrote a registration form and managed to store form data into neo4j DB. I want a login form which can check the registration data and login if user name and password matches ( Authentication ). Does this NeoEloquent cover authentication as well? Here is a part of my code:

Register form code:
<!doctype html>
<html>
<head>
    <title>Laravel Form Validation!</title>

    <!-- load bootstrap -->
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
    <style>
        body    { padding-bottom:40px; padding-top:40px; }
    </style>
</head>
<body class="container">

<div class="row">
    <div class="col-sm-8 col-sm-offset-2">

        <div class="page-header">
            <h1><span class="glyphicon glyphicon-flash"></span> Register!</h1>
        </div>

        @if ($errors->has())
        <div class="alert alert-danger">
            @foreach ($errors->all() as $error)
                {{ $error }}<br>        
            @endforeach
        </div>
        @endif

        <!-- FORM STARTS HERE -->
        <form method="POST" action="/ducks" novalidate>

            {{ Form::token() }}

            <div class="form-group @if ($errors->has('name')) has-error @endif">
                <label for="name">Name</label>
                <input type="text" id="name" class="form-control" name="name" placeholder="Somebody Awesome" value="{{ Input::old('name') }}">
                @if ($errors->has('name')) <p class="help-block">{{ $errors->first('name') }}</p> @endif
            </div>

            <div class="form-group @if ($errors->has('email')) has-error @endif">
                <label for="email">Email</label>
                <input type="text" id="email" class="form-control" name="email" placeholder="[email protected]" value="{{ Input::old('email') }}">
                @if ($errors->has('email')) <p class="help-block">{{ $errors->first('email') }}</p> @endif
            </div>

            <div class="form-group @if ($errors->has('password')) has-error @endif">
                <label for="password">Password</label>
                <input type="password" id="password" class="form-control" name="password">
                @if ($errors->has('password')) <p class="help-block">{{ $errors->first('password') }}</p> @endif
            </div>

            <div class="form-group @if ($errors->has('password_confirm')) has-error @endif">
                <label for="password_confirm">Confirm Password</label>
                <input type="password" id="password_confirm" class="form-control" name="password_confirm">
                @if ($errors->has('password_confirm')) <p class="help-block">{{ $errors->first('password_confirm') }}</p> @endif
            </div>

            <button type="submit" class="btn btn-success">Register!</button>
            <a href="login">Click here to login!</a>
        </form>

    </div>
</div>

</body>
</html>
login.blade.php
<html>
<head>
    <title>Login</title>

    <!-- load bootstrap -->
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
    <style>
        body    { padding-bottom:40px; padding-top:40px; }
    </style>
</head>
<body>

            <form method="POST" action="/login" novalidate>

            {{ Form::token() }}

            User Name: <input type="text" id="usrnme"><br> <br>

            password: <input type="password" id="pword"><br> <br>

            <button id="btn">Login</button>

            </form>


</body>
Routes.php
<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the Closure to execute when that URI is requested.
|
*/

Route::get('/', function()
{
    return View::make('hello');
});

// route to show the duck form
Route::get('ducks', function() 
{
    return View::make('duck-form');
});

// route to process the ducks form
Route::post('ducks', array('before' => 'csrf', function()
{

    // create the validation rules ------------------------
    $rules = array(
        'name'             => 'required',                       // just a normal required validation
        'email'            => 'required|email|unique:ducks',    // required and must be unique in the ducks table
        'password'         => 'required',
        'password_confirm' => 'required|same:password'          // required and has to match the password field
    );

    // create custom validation messages ------------------
    $messages = array(
        'required' => 'The :attribute is really really really important.',
        'same'  => 'The :others must match.'
    );

    // do the validation ----------------------------------
    // validate against the inputs from our form
    $validator = Validator::make(Input::all(), $rules, $messages);

    // check if the validator failed -----------------------
    if ($validator->fails()) {
        // redirect our user back with error messages       
        $messages = $validator->messages();

        // also redirect them back with old inputs so they dont have to fill out the form again
        // but we wont redirect them with the password they entered

        return Redirect::to('ducks')
            ->withErrors($validator)
            ->withInput(Input::except('password', 'password_confirm'));

    } else {
        // validation successful ---------------------------

        // our duck has passed all tests!
        // let him enter the database

        // create the data for our duck
        $duck = new Duck;
        $duck->name     = Input::get('name');
        $duck->email    = Input::get('email');
        $duck->password = Hash::make(Input::get('password'));

        // save our duck
        $duck->save();

        // redirect ----------------------------------------
        // redirect our user back to the form so they can do it all over again
        return Redirect::to('ducks')
            ->with('messages', 'Hooray!');

    }

}));

Route::get('login', function()
{
    return View::make('login');
});

Route::post('login', array('before' => 'csrf', function()
{

    $rules = array(
            'usrnme'             => 'required',                         
            'pword'         => 'required'
        );

    $messages = array(
            'required' => 'The :attribute is really really really important.'
    );

    $validator = Validator::make(Input::all(), $rules, $messages);

    if ($validator->fails()) {
        $messages = $validator->messages();

        return Redirect::to('login')
        ->withErrors($validator)
        ->withInput(Input::except('usrnme', 'pword'));
    }

    if (Auth::attempt($rules)) {
        return Redirect::route('loggedin')
        ->with('flash_notice', 'You are successfully logged in.');
    }

    // authentication failure! lets go back to the login page
    return Redirect::route('login')
    ->with('flash_error', 'Your username/password combination was incorrect.')
    ->withInput();

}));

Route::get('loggedin', function()
{
    return View::make('loggedin');
});

createWith passing multiple relations with same model results in duplicate records

An example is a PhotoAlbum that has multiple photos and one of them is a cover,
Here's a summary bout the model and its relations:

class PhotoAlbum extends NeoEloquent {

   public function cover()
   {
      return $this->hasOne('Photo', 'COVER');
   }

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

Running this code:

PhotoAlbum::create([...], [
   'photos' => [ 'An Array of existing Photo Models Ids'],
   'cover' => 282
]);

And this is how the query looks like:

CREATE (photoalbum:`PhotoAlbum` { title: 'over and over', slug: 'over-and-over') 
WITH photoalbum 
MATCH (with_photo:`Photo`:`Media`), (with_photo:`Photo`:`Media`)
WHERE id(with_photo) IN [282] AND id(with_photo) IN [282] 
CREATE (photoalbum)-[:PHOTO]->(with_photo), (photoalbum)-[:COVER]->(with_photo) 
RETURN photoalbum

That query will create a duplicate of the PhotoAlbum because of this part CREATE (photoalbum)-[:PHOTO]->(with_photo), (photoalbum)-[:COVER]->(with_photo) where it should be something like (photoalbum)-[:PHOTO]->(with_photos_photo), (photoalbum)-[:COVER]->(with_cover_photo) so basically we need the relationship function name prefixing the node variable.

Give support to relationship

HI,

Please give support to relationship entity so we can fetch relationship records.

If already have any suggestion then please let me know ?

Need to store created_at and updated_at in the timestamp

Currently, When I create a node "created_at" and "updated_at" properties will get saved with datetime value like "2015-02-26 13:48:23".

I want to store created_at and updated_at as timestamp like "1424958999193". How can I do this?

Model::create() triggering Model::saving() event

After a few hours of hunting, I found that calling Model::create($my_data) triggers Model::saving(function($model_to_be_created) {} ) instead of Model::creating. $model_to_be_created is also empty, rather than an instance of what will be saved if allowed to continue.

Calling createWith with recursive model relations

class Section extends NeoEloquent {

  public function subSections()
  {
     return $this->hasMany('Section', 'PARENT_OF');
  }

}
Section::createWith([], [
  'subSections' => [sub sections here]
]);

The Cypher generated will result in a duplicate nodes in the same query where Neo4j will complain for not knowing which one we're referencing:

CREATE (section:`Section` { alias: 'blog', title: 'Blog'})
WITH section
MATCH (section:`Section`)
WHERE id(section) IN [1]
CREATE (section)<-[:PARENT_OF]-(section)
RETURN section

The part that errs is

MATCH (section:`Section`)

Where it should be something like MATCH (rel_section:Section)

which makes the query:

CREATE (section:`Section` { alias: 'blog', title: 'Blog'})
WITH section
MATCH (rel_section:`Section`)
WHERE id(rel_section) IN [1]
CREATE (rel_section)<-[:PARENT_OF]-(section)
RETURN section

Database neo4j not configured error:

Hi,
I used installation procedure as you mentioned, wrote a model for form data storing. Here is my model:

<?php

namespace Vinelab\Cms;

class Duck extends NeoEloquent {

protected $fillable = array('name', 'email', 'password');

public function index($name, $email, $password, $password_confirm) {

    $formData = Neo4j::makeNode();
    $formData->setProperty('name',$name)
    ->setProperty('email',$email)
    ->setProperty('password',$password)
    ->setProprty('password_confirm',$password_confirm)
    ->save();

}

}

I gave connections as:
'connections' => [
'neo4j' => [
'driver' => 'neo4j',
'host' => 'localhost',
'port' => '7474',
'username' => null,
'password' => null
]
],
When I submit the data, it returns an error like this:
selection_082
How can I fix it?

exception 'ErrorException' with message 'Undefined index: column'

I am trying to get NeoEloquent working but I got this error every time I use a where(). Found out columnCountForWhereClause() doesn't check if 'column' exists in the $where array, which I temporarily resolved by adding array_key_exists('column', $where) to the return.

    if (is_array($this->wheres))
        return count(array_filter($this->wheres, function($where) use($column) {
            return array_key_exists('column', $where) && $where['column'] == $column;
        }));

Is there a reason nobody issued this before, like am I doing something wrong?

Hyper edge query doc improvement

Hello,

I'm a bit lost with the way I should use HyperEdges.

This is my hyper edge

(u:User)-[:HAS_PERMISSION]->(p:Permission)-[:ON]->(t:Thing)

I'd like to be able to find all the Things on which the User has a particular permission.

Based on the docs

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

I have a reference to the edge after saving it. Question is, how can I later find this edge object ?

Is there any way to write the CYPHER query myself while maintaining neoloquent instances ?

Support for Laravel's Validator

Hello, thanks for creating this package!

I'm trying to make it work with default Laravel's validator.

Setting up rules for Model like:

'email'    => 'unique:users'

Will of course accomplish nothing, but after setting up 'table' as name of node's label:

'email'    => 'unique:User'

Will actually generate a Cypher query:

MATCH (user:User) WHERE user.email = {email} RETURN *

But because by default Laravel's validator (from: Illuminate\Validation\DatabasePresenceVerifier::getCount()) is trying to run:

return $query->count();

there is a problem with default aggregate() from extended Illuminate\Database\Query\Builder:

ErrorException
Undefined index: aggregate

The problem seems to be with:

$results = $this->get($columns);

That should return array:

array (size=1)
  0 => 
    object(stdClass)[372]
      public 'aggregate' => string '1' (length=1) / 0

And instead of it returns:

Everyman\Neo4j\Query\ResultSet

And at this late moment I've stopped researching...

Thanks!

Are you planning to add Illuminate-Schema like migration support for indexes, unique constraints on labels and CREATE UNIQUE?

http://docs.neo4j.org/chunked/stable/cypher-schema.html
http://docs.neo4j.org/chunked/stable/query-create-unique.html

ErrorException thrown when data seeding

At the moment, running the php artisan db:seed command results in:

[ErrorException]                                                             
  Declaration of Vinelab\NeoEloquent\Eloquent\Builder::has() should be compat  
  ible with Illuminate\Database\Eloquent\Builder::has($relation, $operator =   
  '>=', $count = 1, $boolean = 'and', Closure $callback = NULL) 

This can easily be fixed by:

  • adding use Closure; at the top of the Builder class
  • adding PHP type hinting to the $closure argument of the Vinelab\NeoEloquent\Eloquent\Builder::has() method.

Querying nested models

Hello!

I'm struggling with how to query a nested model since join() isn't available. My scenario is this:

A Patient has Appointments
An Appointment has a Doctor

I want to get all patients who have appointments with Dr. Smith.

Normally I'd use join() to join the models together and then do where('appointment.doctor.last_name','=','Smith').

Is there a NeoEloquent way to do this?

Variable type issue

Hi,

NeoEloquent is awesome, thanks for doing!

Could you please check this, because I think probably a bug.

This has no response
$neoBuilder = static::where('age', '=', (int)$age_filter);

whilst this has the proper object:
$neoBuilder = static::where('age', '=', (string)$age_filter);

Could you please check?

Sandor

[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 ?

ORDER BY being entered before the RETURN in Cypher

When querying with orderBy('created_at', 'desc') what we get is something like:

MATCH (article:Article) WHERE article.deleted_at is null order by article.created_at desc RETURN article SKIP 0 LIMIT 10

when it should be

MATCH (article:Article) WHERE article.deleted_at is null RETURN article order by article.created_at desc SKIP 0 LIMIT 10

Must compile ordering after the RETURN and before SKIP and LIMIT

Support Laravel 5 ?

Hey,

It is planned to support ?

just a question... i didn't try anything

thanks

CypherTypeException: Expected a propertycontainer or number here, but got: 11921

I recently hit this issue, though I don't know why it's started to happen "all of a sudden".

I traced it down to when I'm calling "Auth::check()". If I'm signed it, it's pulling my cookie and looking up my user.

The fix I implemented was this:

In vendor/laravel/framework/src/Illuminate/Auth/Guard.php:

    protected function getUserByRecaller($recaller)
    {
            if ($this->validRecaller($recaller) && ! $this->tokenRetrievalAttempted)
            {
                    $this->tokenRetrievalAttempted = true;

                    list($id, $token) = explode('|', $recaller, 2);

                    $this->viaRemember = ! is_null($user = $this->provider->retrieveByToken($id, $token));

                    return $user;
            }
    }

... I fixed the problem by typecasting $id to (int)$id.

Basically Laravel's Auth is calling this:

public function retrieveByToken($identifier, $token)
{
    $model = $this->createModel();

    return $model->newQuery()
                    ->where($model->getKeyName(), $identifier)
                    ->where($model->getRememberTokenName(), $token)
                    ->first();
}

When $identifier is passed to NeoEloquent, however, it's being passed as a string rather than an int. And apparently NeoEloquent doesn't like that.

This hack works for now, but obviously hacking Laravel isn't a good idea. Does this seem like an issue you'd fix within NeoEloquent?

Nested/Eager Loading data via with() not working

Hello,

I'm having trouble getting nested data to work: Example:

$patient = new Patient();
$patient = $patient->with('appointments'); // Works fine
$patient = $patient->with('appointments.doctor'); // Causes "Call to a member function setRelation() on a non-object" if I leave this in
return $patient->get();

As for the relationships:

From Patient:

public function appointments()  // From Patient model
{
    return $this->hasMany('Appointment', 'HAS_APPOINTMENT');
}

From Appointment:

public function patient()  // From Appointment model
{
    return $this->belongsTo('Patient', 'HAS_APPOINTMENT');
}
public function doctor()  // From Appointment model
{
    return $this->belongsTo('Doctor', 'WORKS');
}

From Doctor:

public function appointments()  // From Doctor model
{
    return $this->hasMany('Appointment', 'WORKS');
}

Is there something I'm doing wrong?

Class Not found

Hi, i've installed your package via composer, but for some reason it does not get auto-loaded, can you please instruct me on how to use it?

Thanks,
Rares
screen shot 2014-06-01 at 16 51 14

Can't access batch commands from model

I can't do batch commands by doing $model->prepareBatch() or $model->commitBatch. Both come as undefined functions and rightly so as they are declared in Delegate which is not directly accessible in model.

Relation extends from it, maybe I have to query one to get it. but that would go against the idea of it.

Guarded fields

Hello!
Protecting some fields is really crucial in most of the applications, especially for apps that implements user authentication.
In eloquent we have this;
protected $guarded = ['id', 'password'];

Is it going to be in roadmap as feature?

Can't get models through a property of name 'id'

I don't use the auto incremented id field attached to each node but define another property named id and store a generated number in it. The problem is that, if I do a query on that property to like get the node with a specific id, it returns null while running a cypher query in the neo4j console returns the correct node.

For example;
NeoEloquent query: User::where('id',100000494478771)->get()->first(); // returns null
Cypher query: MATCH (n:User{id:100000494478771}) RETURN n // returns the node

Saving DateTime or Carbon objects

One issue I ran into is that I couldn't save a Carbon object directly to a node without formatting into a string.

Example:
$entity = MyEntity::create([ 'name' => 'something', 'date_established' => Carbon::now() ]);

Neo4j will throw an exception where it is unable to save the date object (of course)

  [Everyman\Neo4j\Exception]
  Unable to create node [400]:
  Headers: Array
  (
      [Content-Type] =>  application/json; charset=UTF-8; stream=true
      [Access-Control-Allow-Origin] =>  *
      [Transfer-Encoding] =>  chunked
      [Server] =>  Jetty(9.0.5.v20130815)
  )
  Body: Array
  (
      [message] => Could not set property "date_established", unsupported type: {date=2014-08-21 13:54:07.000000, timezone_type=3, timezone=UTC}
      [exception] => PropertyValueException
      [fullname] => org.neo4j.server.rest.web.PropertyValueException
      [stacktrace] => Array
          (
              [0] => org.neo4j.server.rest.domain.PropertySettingStrategy.setProperty(PropertySettingStrategy.java:141)
              [1] => org.neo4j.server.rest.domain.PropertySettingStrategy.setProperties(PropertySettingStrategy.java:88)
              [2] => org.neo4j.server.rest.web.DatabaseActions.createNode(DatabaseActions.java:214)
              [3] => org.neo4j.server.rest.web.RestfulGraphDatabase.createNode(RestfulGraphDatabase.java:238)
              [4] => java.lang.reflect.Method.invoke(Method.java:606)
              [5] => org.neo4j.server.rest.transactional.TransactionalRequestDispatcher.dispatch(TransactionalRequestDispatcher.java:139)
              [6] => java.lang.Thread.run(Thread.java:744)
          )
  )

Of course this can be circumvented by converting the Date object to string e.g. Carbon::now()->format('y-m-d');.

But I am wondering if there is a way to automatically detect if a property is an instance of Carbon or DateTime and format it before insert or update without having the user do it. And vice versa when reading data into objects.

Another advantage is that there wont be any potential issues arising from different formatting happening on dates.

Thanks!

Examples on deletion,and Relations properties

Could you add some examples to documentaion or on problems below:
1.How do you delete a node?
2.How do you delete a node's relations?
3.How do you search in database based on a specific property of a node? for example,suppose that you have 10 nodes,each one stores name,email,and phone number of a person,how do you find the node that it's name property is "john"?
4.How do you define and access properties of relations?
It would be great if you added some examples to documentation.thanks in advance.

Bug when restoring a Soft Deleted Node

Hello !

I have maybe a bug with the soft deleting.
When I'm trying to restore a deleted node :

$user = User::onlyTrashed()->where('id', '=', intval($params['user_id']))->first();

if(!$user)
{
    return 'error';
}

$user->restore();

I have an error on $user->restore(); :

[2014-08-21 10:45:24] api.ERROR: exception 'Vinelab\NeoEloquent\QueryException' with message 'ParameterNotFoundException: Expected a parameter named deleted_at' in /vagrant/www/myproject/vendor/vinelab/neoeloquent/src/Vinelab/NeoEloquent/Connection.php:420
Stack trace:
#0 /vagrant/www/myproject/vendor/vinelab/neoeloquent/src/Vinelab/NeoEloquent/Connection.php(172): Vinelab\NeoEloquent\Connection->run('MATCH (user:`Us...', Array, Object(Closure))
#1 /vagrant/www/myproject/vendor/laravel/framework/src/Illuminate/Database/Connection.php(314): Vinelab\NeoEloquent\Connection->affectingStatement('MATCH (user:`Us...', Array)
#2 /vagrant/www/myproject/vendor/vinelab/neoeloquent/src/Vinelab/NeoEloquent/Query/Builder.php(139): Illuminate\Database\Connection->update('MATCH (user:`Us...', Array)
#3 /vagrant/www/myproject/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(318): Vinelab\NeoEloquent\Query\Builder->update(Array)
#4 /vagrant/www/myproject/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1453): Illuminate\Database\Eloquent\Builder->update(Array)
#5 /vagrant/www/myproject/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1387): Illuminate\Database\Eloquent\Model->performUpdate(Object(Vinelab\NeoEloquent\Eloquent\Builder))
#6 /vagrant/www/myproject/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletingTrait.php(89): Illuminate\Database\Eloquent\Model->save()
#7 /vagrant/www/myproject/app/controllers/api/UserController.php(127): User->restore()
#8 [internal function]: UserController->restore(1408617924.7433)

I don't understand why this error happen. Is there a Match performed on the database after the update, trying to get the deleted 'deleted_at' property ?

Thank you in advance for your help, and thanks for this nice library !

neo4jphp processor

Why not introduce additional post processor like Illuminate\Database\Query\Processors\Processor have to offer for few database drivers, and separete the logic behind neo4jphp client and NeoEloquent itself?

With this most of methods from Eloquent's Illuminate\Database\Query\Builder would work out of the box - instead returning on ResultSet now and then since builders extend the default Illuminate builders.

Please can you predict if this is right approach for your project/the code goal is still maintained?

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.

Auth::check() causes exception

I'm trying to implement standard Laravel Authentication using NeoEloquent and I keep getting the exception:

CypherTypeException: Expected a propertycontainer or number here, but got: email

My code has a route that goes to a controller, and I've debugged the issue down to the line of code "Auth::check()". If I comment it out, the code works. (Though not as intended)

One point of interest - if I run Auth::attempt() before I run Auth::check(), Auth::check() won't crash. But I'm guessing because the user is already in memory at that point and doesn't need to be queried from the 'database'.

I'm open to my User model being misconfigured, but if so, I'm not sure how. Seems like it is a NeoEloquent specific issue. Here is my user class:

class User extends NeoEloquent implements UserInterface, RemindableInterface {

public function getAuthIdentifier()
{
    return $this->email;
}

public function getAuthPassword()
{
    return $this->password;
}

public function getRememberToken()
{
    return $this->remember_token;
}

public function setRememberToken($token)
{
    $this->remember_token = $token;
}

public function getRememberTokenName()
{
    return 'remember_token';
}

public function getReminderEmail()
{
    return $this->email;
}
}

Relation issue

I have two classes in relation. Appusers could have multiple Apps. When I tried to get one of the User's App object I have empty collection.

class AppFrameApp extends NeoEloquent {

}

class AppFrameUser extends NeoEloquent {

public function getApp($app_id)
{
$appFrameApp = $this->appframe_apps()->where('app_id','=',$app_id)->get();
}

public function appframe_apps()
{
$this->hasMany('AppFrameApp', 'HAS');
}
}

When I check what's happening I spotted the Cypher query is the following:

MATCH (appframeuser:AppFrameUser), (appframeuser)-[rel_has_appframe_apps:HAS]->(appframe_apps:AppFrameApp) WHERE id(appframeuser) = {idappframeuser} and appframeuser.app_id = {app_id} RETURN appframe_apps

As you can see the second condition / appframeuser.app_id = {app_id} / is wrong. I expect this: appframeapp.app_id = {app_id}

Maybe I missunderstand something. Could you please advise me?

Sandor

Querying relations

When querying a relation the query falls onto the parent model instead of the relation model.

$student = Student::first();
$student->images()->where('description', '=', '123')->get();

Will result in the following query:

MATCH (student:`Student`), (student)-[rel_images_images:IMAGES]->(images:`Images`) WHERE id(student) = {idstudent} and student.description = {description} RETURN images

While it should be:

MATCH (student:`Student`), (student)-[rel_images_images:IMAGES]->(images:`Images`) WHERE id(student) = {idstudent} and images.description = {description} RETURN images

createWith giving Data Missing error

In some cases createWith gives the Data Missing Error when relations are existing models. If I do, create and relations separately, it works.

For example;
Doesn't work;
Invite::createWith(['id' => $invite_id, 'accepted' => $accepted],['user' => array($user), 'product => array($product)]);

Works;
$invite_node = Invite::create(['id' => $invite_id, 'accepted' => $accepted]);
$invite_node->user()->save($user);
$invite_node->product()->save($product);

$query->update($attribtues) conflicts between binding values.

Example:

$result = $query->where('publish_date','<=', Carbon::now()->toDateTimeString());
$result = $result->where('publish_state','=','scheduled');
$result = $result->update([
      'publish_state' => 'published'
]);

In this case the bindings for publish_state will be overridden when sent to the neo4jphp driver and only one of them will be used. In this case it was the scheduled.

["bindings"]=> array(3) { ["publish_state"]=> string(9) "scheduled" ["updated_at"]=> string(19) "2014-08-26 15:27:36" ["publish_date"]=> string(19) "2014-08-26 18:27:36"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.