SlideShare a Scribd company logo
Leveraging the Power of
Graph Databases
in PHP
Jeremy Kendall
Nashville PHP
November 2014
Obligatory Intro Slide
Also - New Father
What Kind of
Database?
Graphs != Charts
https://www.flickr.com/photos/markgroves/3065192499/
Graphs != Charts
http://stephenwildish.tumblr.com/post/101408321763/friday-project-witch-moral-compass
Graph Databases
Graph Databases
• Data Model!
• Nodes with properties
• Typed relationships
Graph Databases
• Data Model!
• Nodes with properties
• Typed relationships
• Strengths!
• Highly connected data
• ACID
Graph Databases
• Data Model!
• Nodes with properties
• Typed relationships
• Strengths!
• Highly connected data
• ACID
• Weaknesses!
• Paradigm shift
Graph Databases
• Data Model!
• Nodes with properties
• Typed relationships
• Strengths!
• Highly connected data
• ACID
• Weaknesses!
• Paradigm shift
• Examples!
• Neo4j, Titan, OrientDB
Why Care?
Why Care?
• All the NoSQL Joy
• Schema-less
• Semi-structured data
Why Care?
• All the NoSQL Joy
• Schema-less
• Semi-structured data
• Escape from JOIN Hell
Why Care?
• All the NoSQL Joy
• Schema-less
• Semi-structured data
• Escape from JOIN Hell
• Speed
Why Care?
Why Care?
• Relationships have 1st class status
Why Care?
• Relationships have 1st class status
• Just as important as the objects connecting them
Why Care?
• Relationships have 1st class status
• Just as important as the objects connecting them
• You can have properties & labels
Why Care?
• Relationships have 1st class status
• Just as important as the objects connecting them
• You can have properties & labels
• Multiple relationships
Why Care?
Speed
Depth MySQL Query Time Neo4j Query Time Records Returned
2 0.028 (28 MS) 0.04 ~900
3 0.213 0.06 ~999
4 10.273 0.07 ~999
5 92.613 0.07 ~999
1,000 people with an average 50 friends each
Crazy Speed
Depth MySQL Query Time Neo4j Query Time Records Returned
2 0.016 (16 MS) 0.01 ~2500
3 30.27 0.168 ~125,000
4 1543.505 1.359 ~600,000
5 Stopped after 1 hour 2.132 ~800,000
1,000,000 people with an average 50 friends each
Neo4j + Cypher
Cypher
• Neo4j’s declarative query language
• Easy to pick up
• Some clauses and concepts familiar from SQL
Simple Example
Goal
Create Some Nodes
CREATE (jk:Person { name: "Jeremy Kendall" })!
CREATE (gs:Company { name: "Graph Story" })!
!
CREATE (tn:State { name: "Tennessee" })!
CREATE (memphis:City { name: "Memphis" })!
CREATE (nashville:City { name: "Nashville" })!
!
CREATE (hotchicken:Food { name: "Hot Chicken" })!
CREATE (bbq:Food { name: "Barbecue" })!
CREATE (photography:Hobby { name: "Photography" })!
CREATE (language:Language { name: "PHP" })!
!
// . . . snip . . .!
Create Some Relationships
// . . . snip . . .!
!
CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),!
(jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),!
(hotchicken)-[:ONLY_IN]->(nashville),!
(bbq)-[:ONLY_IN]->(memphis),!
(jk)-[:LOVES]->(hotchicken),!
!
// . . . snip . . .!
Create Some Relationships
// . . . snip . . .!
!
CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),!
(jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),!
(hotchicken)-[:ONLY_IN]->(nashville),!
(bbq)-[:ONLY_IN]->(memphis),!
(jk)-[:LOVES]->(hotchicken),!
!
// . . . snip . . .!
Create Some Relationships
// . . . snip . . .!
!
CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),!
(jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),!
(hotchicken)-[:ONLY_IN]->(nashville),!
(bbq)-[:ONLY_IN]->(memphis),!
(jk)-[:LOVES]->(hotchicken),!
!
// . . . snip . . .!
Create Some Relationships
// . . . snip . . .!
!
CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),!
(jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),!
(hotchicken)-[:ONLY_IN]->(nashville),!
(bbq)-[:ONLY_IN]->(memphis),!
(jk)-[:LOVES]->(hotchicken),!
!
// . . . snip . . .!
Example Cypher Query
MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)!
WITH p, l!
MATCH (p)-[:WORKS_AT]->(j)!
WITH p, l, j!
MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)!
RETURN p, l, j, o
Example Cypher Query
MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)!
WITH p, l!
MATCH (p)-[:WORKS_AT]->(j)!
WITH p, l, j!
MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)!
RETURN p, l, j, o
Example Cypher Query
MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)!
WITH p, l!
MATCH (p)-[:WORKS_AT]->(j)!
WITH p, l, j!
MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)!
RETURN p, l, j, o
Example Cypher Query
MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)!
WITH p, l!
MATCH (p)-[:WORKS_AT]->(j)!
WITH p, l, j!
MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)!
RETURN p, l, j, o
Example Cypher Query
MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)!
WITH p, l!
MATCH (p)-[:WORKS_AT]->(j)!
WITH p, l, j!
MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)!
RETURN p, l, j, o
Example Cypher Query
MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)!
WITH p, l!
MATCH (p)-[:WORKS_AT]->(j)!
WITH p, l, j!
MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)!
RETURN p, l, j, o
Example Cypher Query
MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)!
WITH p, l!
MATCH (p)-[:WORKS_AT]->(j)!
WITH p, l, j!
MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)!
RETURN p, l, j, o
Query Result
Neo4j + PHP
Neo4jPHP
• PHP wrapper for the Neo4j REST API
• Installable via Composer
• Used internally at Graph Story
• Used in this presentation
• Well tested
• https://packagist.org/packages/everyman/
neo4jphp
Also see: NeoClient
• Written by Neoxygen
• Alternative PHP wrapper for the Neo4j REST API
• Installable via Composer
• Under review for internal use at Graph Story
• Well tested
• https://packagist.org/packages/neoxygen/neoclient
Connecting
$neo4jClient = new EverymanNeo4jClient(!
‘yourgraph.example.com’, !
7473!
);!
!
$neo4jClient->getTransport()!
->setAuth('username', 'password')!
->getTransport()->useHttps();
Creating a Node and Label
$node = new Node($neo4jClient);!
!
$label = $neo4jClient->makeLabel('Person');!
!
$node->setProperty('name', ‘Jeremy Kendall');!
!
$node->save()->addLabels(array($label));
Searching
// Searching for a label by property!
$label = $neo4jClient->makeLabel('Person');!
$nodes = $label->getNodes('name', $name);
Querying (Cypher)
$queryString = !
'MATCH (p:Person { name: { name }}) RETURN p';!
!
$query = new EverymanNeo4jCypherQuery(!
$neo4jClient,!
$queryString,!
['name' => ‘Jeremy Kendall']!
);!
!
$result = $query->getResultSet();
Named Parameters
Named Parameters
$queryString = !
'MATCH (p:Person { name: { name }}) RETURN p';!
!
$query = new EverymanNeo4jCypherQuery(!
$neo4jClient,!
$queryString,!
['name' => ‘Jeremy Kendall']!
);!
!
$result = $query->getResultSet();
Named Parameters
$queryString = !
'MATCH (p:Person { name: { name }}) RETURN p';!
!
$query = new EverymanNeo4jCypherQuery(!
$neo4jClient,!
$queryString,!
['name' => ‘Jeremy Kendall']!
);!
!
$result = $query->getResultSet();
Content Modeling:
News Feeds
News Feed
• Modeled as a list of posts
• Newest post first
• All subsequent posts follow
• Relationships: LASTPOST and NEXTPOST
LASTPOST
NEXTPOST
The Content Model
class Content!
{!
public $node;!
public $nodeId;!
public $contentId;!
public $title;!
public $url;!
public $tagstr;!
public $timestamp;!
public $userNameForPost;!
public $owner = false;!
}
Adding Content
public static function add($username, Content $content)!
{!
$queryString =<<<CYPHER!
MATCH (user { username: {u}})!
OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)!
DELETE r!
CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:
{contentId} })!
WITH p, collect(lastpost) as lastposts!
FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)!
RETURN p, {u} as username, true as owner!
CYPHER;!
!
$query = new Query(!
Neo4jClient::client(),!
$queryString,!
array(!
'u' => $username,!
'title' => $content->title,!
'url' => $content->url,!
'tagstr' => $content->tagstr,!
'timestamp' => time(),!
'contentId' => uniqid()!
)!
);!
$result = $query->getResultSet();!
!
return self::returnMappedContent($result);!
}
Adding Content
public static function add($username, Content $content)!
{!
$queryString =<<<CYPHER!
MATCH (user { username: {u}})!
OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)!
DELETE r!
CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:
{contentId} })!
WITH p, collect(lastpost) as lastposts!
FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)!
RETURN p, {u} as username, true as owner!
CYPHER;!
!
$query = new Query(!
Neo4jClient::client(),!
$queryString,!
array(!
'u' => $username,!
'title' => $content->title,!
'url' => $content->url,!
'tagstr' => $content->tagstr,!
'timestamp' => time(),!
'contentId' => uniqid()!
)!
);!
$result = $query->getResultSet();!
!
return self::returnMappedContent($result);!
}
Adding Content
public static function add($username, Content $content)!
{!
$queryString =<<<CYPHER!
MATCH (user { username: {u}})!
OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)!
DELETE r!
CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:
{contentId} })!
WITH p, collect(lastpost) as lastposts!
FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)!
RETURN p, {u} as username, true as owner!
CYPHER;!
!
$query = new Query(!
Neo4jClient::client(),!
$queryString,!
array(!
'u' => $username,!
'title' => $content->title,!
'url' => $content->url,!
'tagstr' => $content->tagstr,!
'timestamp' => time(),!
'contentId' => uniqid()!
)!
);!
$result = $query->getResultSet();!
!
return self::returnMappedContent($result);!
}
Adding Content
MATCH (user { username: {u}})!
OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)!
DELETE r!
CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:
{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:
{contentId} })!
WITH p, collect(lastpost) as lastposts!
FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)!
RETURN p, {u} as username, true as owner
Adding Content
MATCH (user { username: {u}})!
OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)!
DELETE r!
CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:
{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:
{contentId} })!
WITH p, collect(lastpost) as lastposts!
FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)!
RETURN p, {u} as username, true as owner
Adding Content
MATCH (user { username: {u}})!
OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)!
DELETE r!
CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:
{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:
{contentId} })!
WITH p, collect(lastpost) as lastposts!
FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)!
RETURN p, {u} as username, true as owner
Adding Content
MATCH (user { username: {u}})!
OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)!
DELETE r!
CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:
{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:
{contentId} })!
WITH p, collect(lastpost) as lastposts!
FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)!
RETURN p, {u} as username, true as owner
Adding Content
MATCH (user { username: {u}})!
OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)!
DELETE r!
CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:
{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:
{contentId} })!
WITH p, collect(lastpost) as lastposts!
FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)!
RETURN p, {u} as username, true as owner
Adding Content
MATCH (user { username: {u}})!
OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)!
DELETE r!
CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:
{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:
{contentId} })!
WITH p, collect(lastpost) as lastposts!
FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)!
RETURN p, {u} as username, true as owner
Adding Content
$query = new Query(!
$neo4jClient,!
$queryString,!
array(!
'u' => $username,!
'title' => $content->title,!
'url' => $content->url,!
'tagstr' => $content->tagstr,!
'timestamp' => time(),!
'contentId' => uniqid()!
)!
);!
!
$result = $query->getResultSet();
Retrieving Content
public static function getContent($username, $skip)!
{!
$queryString = <<<CYPHER!
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f!
WITH DISTINCT f, u!
MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p!
RETURN p, f.username as username, f = u as owner!
ORDER BY p.timestamp desc SKIP { skip } LIMIT 4!
CYPHER;!
!
$query = new Query(!
Neo4jClient::client(),!
$queryString,!
array(!
'u' => $username,!
'skip' => $skip,!
)!
);!
!
$result = $query->getResultSet();!
!
return self::returnMappedContent($result);!
}
Retrieving Content
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f!
WITH DISTINCT f, u!
MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p!
RETURN p, f.username as username, f = u as owner!
ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Retrieving Content
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f!
WITH DISTINCT f, u!
MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p!
RETURN p, f.username as username, f = u as owner!
ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Retrieving Content
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f!
WITH DISTINCT f, u!
MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p!
RETURN p, f.username as username, f = u as owner!
ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Retrieving Content
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f!
WITH DISTINCT f, u!
MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p!
RETURN p, f.username as username, f = u as owner!
ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Retrieving Content
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f!
WITH DISTINCT f, u!
MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p!
RETURN p, f.username as username, f = u as owner!
ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Retrieving Content
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f!
WITH DISTINCT f, u!
MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p!
RETURN p, f.username as username, f = u as owner!
ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Editing Content
public static function edit(Content $content)!
{!
$updatedAt = time();!
!
$node = $content->node;!
$node->setProperty('title', $content->title);!
$node->setProperty('url', $content->url);!
$node->setProperty('tagstr', $content->tagstr);!
$node->setProperty('updated', $updatedAt);!
$node->save();!
!
$content->updated = $updatedAt;!
!
return $content;!
}
Editing Content
public static function edit(Content $content)!
{!
$updatedAt = time();!
!
$node = $content->node;!
$node->setProperty('title', $content->title);!
$node->setProperty('url', $content->url);!
$node->setProperty('tagstr', $content->tagstr);!
$node->setProperty('updated', $updatedAt);!
$node->save();!
!
$content->updated = $updatedAt;!
!
return $content;!
}
Editing Content
public static function edit(Content $content)!
{!
$updatedAt = time();!
!
$node = $content->node;!
$node->setProperty('title', $content->title);!
$node->setProperty('url', $content->url);!
$node->setProperty('tagstr', $content->tagstr);!
$node->setProperty('updated', $updatedAt);!
$node->save();!
!
$content->updated = $updatedAt;!
!
return $content;!
}
Editing Content
public static function edit(Content $content)!
{!
$updatedAt = time();!
!
$node = $content->node;!
$node->setProperty('title', $content->title);!
$node->setProperty('url', $content->url);!
$node->setProperty('tagstr', $content->tagstr);!
$node->setProperty('updated', $updatedAt);!
$node->save();!
!
$content->updated = $updatedAt;!
!
return $content;!
}
Editing Content
public static function edit(Content $content)!
{!
$updatedAt = time();!
!
$node = $content->node;!
$node->setProperty('title', $content->title);!
$node->setProperty('url', $content->url);!
$node->setProperty('tagstr', $content->tagstr);!
$node->setProperty('updated', $updatedAt);!
$node->save();!
!
$content->updated = $updatedAt;!
!
return $content;!
}
Deleting Content
public static function delete($username, $contentId)!
{!
$queryString = self::getDeleteQueryString(!
$username, !
$contentId!
);!
!
$params = array(!
'username' => $username,!
'contentId' => $contentId,!
);!
!
$query = new Query(!
$neo4jClient,!
$queryString, !
$params!
);!
$query->getResultSet();!
}
Deleting Content
public static function delete($username, $contentId)!
{!
$queryString = self::getDeleteQueryString(!
$username, !
$contentId!
);!
!
$params = array(!
'username' => $username,!
'contentId' => $contentId,!
);!
!
$query = new Query(!
$neo4jClient,!
$queryString, !
$params!
);!
$query->getResultSet();!
}
Deleting Content
public static function delete($username, $contentId)!
{!
$queryString = self::getDeleteQueryString(!
$username, !
$contentId!
);!
!
$params = array(!
'username' => $username,!
'contentId' => $contentId,!
);!
!
$query = new Query(!
$neo4jClient,!
$queryString, !
$params!
);!
$query->getResultSet();!
}
Deleting Content
public static function delete($username, $contentId)!
{!
$queryString = self::getDeleteQueryString(!
$username, !
$contentId!
);!
!
$params = array(!
'username' => $username,!
'contentId' => $contentId,!
);!
!
$query = new Query(!
$neo4jClient,!
$queryString, !
$params!
);!
$query->getResultSet();!
}
Deleting Content: Leaf
// If leaf!
MATCH (u:User { username: { username }})-[:LASTPOST|
NEXTPOST*0..]->(c:Content { contentId: { contentId }})!
WITH c!
MATCH (c)-[r]-()!
DELETE c, r
Deleting Content: Leaf
// If leaf!
MATCH (u:User { username: { username }})-[:LASTPOST|
NEXTPOST*0..]->(c:Content { contentId: { contentId }})!
WITH c!
MATCH (c)-[r]-()!
DELETE c, r
Deleting Content: Leaf
// If leaf!
MATCH (u:User { username: { username }})-[:LASTPOST|
NEXTPOST*0..]->(c:Content { contentId: { contentId }})!
WITH c!
MATCH (c)-[r]-()!
DELETE c, r
Deleting Content: Leaf
// If leaf!
MATCH (u:User { username: { username }})-[:LASTPOST|
NEXTPOST*0..]->(c:Content { contentId: { contentId }})!
WITH c!
MATCH (c)-[r]-()!
DELETE c, r
Deleting Content: Leaf
// If leaf!
MATCH (u:User { username: { username }})-[:LASTPOST|
NEXTPOST*0..]->(c:Content { contentId: { contentId }})!
WITH c!
MATCH (c)-[r]-()!
DELETE c, r
Deleting Content: LASTPOST
// If last!
MATCH (u:User { username: { username }})-[lp:LASTPOST]-
>(del:Content { contentId: { contentId }})-[np:NEXTPOST]-
>(nextPost)!
CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)!
DELETE lp, del, np
Deleting Content: LASTPOST
// If last!
MATCH (u:User { username: { username }})-[lp:LASTPOST]-
>(del:Content { contentId: { contentId }})-[np:NEXTPOST]-
>(nextPost)!
CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)!
DELETE lp, del, np
Deleting Content: LASTPOST
// If last!
MATCH (u:User { username: { username }})-[lp:LASTPOST]-
>(del:Content { contentId: { contentId }})-[np:NEXTPOST]-
>(nextPost)!
CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)!
DELETE lp, del, np
Deleting Content: LASTPOST
// If last!
MATCH (u:User { username: { username }})-[lp:LASTPOST]-
>(del:Content { contentId: { contentId }})-[np:NEXTPOST]-
>(nextPost)!
CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)!
DELETE lp, del, np
Deleting Content: Other
// All other!
MATCH (u:User { username: { username }})-[:LASTPOST|
NEXTPOST*0..]->(before),!
(before)-[delBefore]->(del:Content { contentId:
{ contentId }})-[delAfter]->(after)!
CREATE UNIQUE (before)-[:NEXTPOST]->(after)!
DELETE del, delBefore, delAfter
Deleting Content: Other
// All other!
MATCH (u:User { username: { username }})-[:LASTPOST|
NEXTPOST*0..]->(before),!
(before)-[delBefore]->(del:Content { contentId:
{ contentId }})-[delAfter]->(after)!
CREATE UNIQUE (before)-[:NEXTPOST]->(after)!
DELETE del, delBefore, delAfter
Deleting Content: Other
// All other!
MATCH (u:User { username: { username }})-[:LASTPOST|
NEXTPOST*0..]->(before),!
(before)-[delBefore]->(del:Content { contentId:
{ contentId }})-[delAfter]->(after)!
CREATE UNIQUE (before)-[:NEXTPOST]->(after)!
DELETE del, delBefore, delAfter
Deleting Content: Other
// All other!
MATCH (u:User { username: { username }})-[:LASTPOST|
NEXTPOST*0..]->(before),!
(before)-[delBefore]->(del:Content { contentId:
{ contentId }})-[delAfter]->(after)!
CREATE UNIQUE (before)-[:NEXTPOST]->(after)!
DELETE del, delBefore, delAfter
Deleting Content: Other
// All other!
MATCH (u:User { username: { username }})-[:LASTPOST|
NEXTPOST*0..]->(before),!
(before)-[delBefore]->(del:Content { contentId:
{ contentId }})-[delAfter]->(after)!
CREATE UNIQUE (before)-[:NEXTPOST]->(after)!
DELETE del, delBefore, delAfter
Questions?
Thanks!
!
jeremy.kendall@graphstory.com
@JeremyKendall
http://www.graphstory.com

More Related Content

ODP
Php 102: Out with the Bad, In with the Good
PDF
Assetic (OSCON)
PDF
The jQuery Divide
PDF
NoSQL & MongoDB
KEY
Potential Friend Finder
PDF
The effective use of Django ORM
PDF
NoSQL を Ruby で実践するための n 個の方法
Php 102: Out with the Bad, In with the Good
Assetic (OSCON)
The jQuery Divide
NoSQL & MongoDB
Potential Friend Finder
The effective use of Django ORM
NoSQL を Ruby で実践するための n 個の方法

What's hot (15)

ODP
Terms of endearment - the ElasticSearch Query DSL explained
KEY
(Ab)Using the MetaCPAN API for Fun and Profit
PDF
Tips of CakePHP and MongoDB - Cakefest2011 ichikaway
PPT
Fantom and Tales
PDF
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...
PPT
PHP Tutorial (funtion)
PDF
Current state-of-php
PDF
How to use MongoDB with CakePHP
PDF
Fazendo mágica com ElasticSearch
PDF
jQuery%20on%20Rails%20Presentation
PDF
10gen Presents Schema Design and Data Modeling
PPTX
Raleigh Web Design Meetup Group - Sass Presentation
PPTX
SPL - The Undiscovered Library - PHPBarcelona 2015
PDF
My First Ruby
PDF
анатолий шарифулин Mojolicious
Terms of endearment - the ElasticSearch Query DSL explained
(Ab)Using the MetaCPAN API for Fun and Profit
Tips of CakePHP and MongoDB - Cakefest2011 ichikaway
Fantom and Tales
RESTFUL SERVICES MADE EASY: THE EVE REST API FRAMEWORK - Nicola Iarocci - Co...
PHP Tutorial (funtion)
Current state-of-php
How to use MongoDB with CakePHP
Fazendo mágica com ElasticSearch
jQuery%20on%20Rails%20Presentation
10gen Presents Schema Design and Data Modeling
Raleigh Web Design Meetup Group - Sass Presentation
SPL - The Undiscovered Library - PHPBarcelona 2015
My First Ruby
анатолий шарифулин Mojolicious
Ad

Viewers also liked (7)

PDF
Illinois Health Care Spring It Technology Conference
ODP
A Brief Introduction to Zend_Form
ODP
Zero to Zend Framework in 10 minutes
ODP
TDD in PHP - Memphis PHP 2011-08-25
ODP
Intro to #memtech PHP 2011-12-05
ODP
Tdd in php a brief example
PDF
Didactica concepto, objeto y finalidades
Illinois Health Care Spring It Technology Conference
A Brief Introduction to Zend_Form
Zero to Zend Framework in 10 minutes
TDD in PHP - Memphis PHP 2011-08-25
Intro to #memtech PHP 2011-12-05
Tdd in php a brief example
Didactica concepto, objeto y finalidades
Ad

Similar to Leveraging the Power of Graph Databases in PHP (20)

PDF
Leveraging the Power of Graph Databases in PHP
PDF
Neo4j (Part 1)
PDF
Neo4j Introduction (Basics, Cypher, RDBMS to GRAPH)
PDF
Getting started with Graph Databases & Neo4j
PDF
Introduction to graph databases, Neo4j and Spring Data - English 2015 Edition
PPT
Hands on Training – Graph Database with Neo4j
PDF
Neo4j: Graph-like power
PDF
3rd Athens Big Data Meetup - 2nd Talk - Neo4j: The World's Leading Graph DB
PPTX
Intro to Cypher
PDF
Neo4j Introduction (for Techies)
PDF
Combine Spring Data Neo4j and Spring Boot to quickl
PDF
Understanding Graph Databases with Neo4j and Cypher
PDF
Polyglot Persistence with MongoDB and Neo4j
PDF
Graph Database Using Neo4J
PDF
Neo4J
PPTX
Neo4j graph database
PPTX
The Inside Scoop on Neo4j: Meet the Builders
PDF
Introduction to Graph Databases with Neo4J
PDF
There and Back Again, A Developer's Tale
Leveraging the Power of Graph Databases in PHP
Neo4j (Part 1)
Neo4j Introduction (Basics, Cypher, RDBMS to GRAPH)
Getting started with Graph Databases & Neo4j
Introduction to graph databases, Neo4j and Spring Data - English 2015 Edition
Hands on Training – Graph Database with Neo4j
Neo4j: Graph-like power
3rd Athens Big Data Meetup - 2nd Talk - Neo4j: The World's Leading Graph DB
Intro to Cypher
Neo4j Introduction (for Techies)
Combine Spring Data Neo4j and Spring Boot to quickl
Understanding Graph Databases with Neo4j and Cypher
Polyglot Persistence with MongoDB and Neo4j
Graph Database Using Neo4J
Neo4J
Neo4j graph database
The Inside Scoop on Neo4j: Meet the Builders
Introduction to Graph Databases with Neo4J
There and Back Again, A Developer's Tale

More from Jeremy Kendall (9)

PDF
5 Ways to Awesome-ize Your (PHP) Code
PDF
Game Changing Dependency Management
PDF
Keeping it small - Getting to know the Slim PHP micro framework
PDF
Keeping it Small: Getting to know the Slim Micro Framework
KEY
Keeping it small: Getting to know the Slim micro framework
KEY
Php 101: PDO
ODP
PHP 102: Out with the Bad, In with the Good
ODP
Zend_Form to the Rescue - A Brief Introduction to Zend_Form
PDF
Zero to ZF in 10 Minutes
5 Ways to Awesome-ize Your (PHP) Code
Game Changing Dependency Management
Keeping it small - Getting to know the Slim PHP micro framework
Keeping it Small: Getting to know the Slim Micro Framework
Keeping it small: Getting to know the Slim micro framework
Php 101: PDO
PHP 102: Out with the Bad, In with the Good
Zend_Form to the Rescue - A Brief Introduction to Zend_Form
Zero to ZF in 10 Minutes

Recently uploaded (20)

PDF
Shreyas Phanse Resume: Experienced Backend Engineer | Java • Spring Boot • Ka...
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PPT
Teaching material agriculture food technology
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Encapsulation theory and applications.pdf
PDF
CIFDAQ's Market Insight: SEC Turns Pro Crypto
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PPTX
Cloud computing and distributed systems.
PDF
Machine learning based COVID-19 study performance prediction
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
Empathic Computing: Creating Shared Understanding
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Electronic commerce courselecture one. Pdf
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PPTX
Big Data Technologies - Introduction.pptx
Shreyas Phanse Resume: Experienced Backend Engineer | Java • Spring Boot • Ka...
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Teaching material agriculture food technology
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Encapsulation_ Review paper, used for researhc scholars
Encapsulation theory and applications.pdf
CIFDAQ's Market Insight: SEC Turns Pro Crypto
20250228 LYD VKU AI Blended-Learning.pptx
Cloud computing and distributed systems.
Machine learning based COVID-19 study performance prediction
Unlocking AI with Model Context Protocol (MCP)
Empathic Computing: Creating Shared Understanding
Reach Out and Touch Someone: Haptics and Empathic Computing
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Diabetes mellitus diagnosis method based random forest with bat algorithm
Electronic commerce courselecture one. Pdf
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Understanding_Digital_Forensics_Presentation.pptx
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Big Data Technologies - Introduction.pptx

Leveraging the Power of Graph Databases in PHP

  • 1. Leveraging the Power of Graph Databases in PHP Jeremy Kendall Nashville PHP November 2014
  • 3. Also - New Father
  • 8. Graph Databases • Data Model! • Nodes with properties • Typed relationships
  • 9. Graph Databases • Data Model! • Nodes with properties • Typed relationships • Strengths! • Highly connected data • ACID
  • 10. Graph Databases • Data Model! • Nodes with properties • Typed relationships • Strengths! • Highly connected data • ACID • Weaknesses! • Paradigm shift
  • 11. Graph Databases • Data Model! • Nodes with properties • Typed relationships • Strengths! • Highly connected data • ACID • Weaknesses! • Paradigm shift • Examples! • Neo4j, Titan, OrientDB
  • 13. Why Care? • All the NoSQL Joy • Schema-less • Semi-structured data
  • 14. Why Care? • All the NoSQL Joy • Schema-less • Semi-structured data • Escape from JOIN Hell
  • 15. Why Care? • All the NoSQL Joy • Schema-less • Semi-structured data • Escape from JOIN Hell • Speed
  • 17. Why Care? • Relationships have 1st class status
  • 18. Why Care? • Relationships have 1st class status • Just as important as the objects connecting them
  • 19. Why Care? • Relationships have 1st class status • Just as important as the objects connecting them • You can have properties & labels
  • 20. Why Care? • Relationships have 1st class status • Just as important as the objects connecting them • You can have properties & labels • Multiple relationships
  • 22. Speed Depth MySQL Query Time Neo4j Query Time Records Returned 2 0.028 (28 MS) 0.04 ~900 3 0.213 0.06 ~999 4 10.273 0.07 ~999 5 92.613 0.07 ~999 1,000 people with an average 50 friends each
  • 23. Crazy Speed Depth MySQL Query Time Neo4j Query Time Records Returned 2 0.016 (16 MS) 0.01 ~2500 3 30.27 0.168 ~125,000 4 1543.505 1.359 ~600,000 5 Stopped after 1 hour 2.132 ~800,000 1,000,000 people with an average 50 friends each
  • 25. Cypher • Neo4j’s declarative query language • Easy to pick up • Some clauses and concepts familiar from SQL
  • 27. Goal
  • 28. Create Some Nodes CREATE (jk:Person { name: "Jeremy Kendall" })! CREATE (gs:Company { name: "Graph Story" })! ! CREATE (tn:State { name: "Tennessee" })! CREATE (memphis:City { name: "Memphis" })! CREATE (nashville:City { name: "Nashville" })! ! CREATE (hotchicken:Food { name: "Hot Chicken" })! CREATE (bbq:Food { name: "Barbecue" })! CREATE (photography:Hobby { name: "Photography" })! CREATE (language:Language { name: "PHP" })! ! // . . . snip . . .!
  • 29. Create Some Relationships // . . . snip . . .! ! CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),! (jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),! (hotchicken)-[:ONLY_IN]->(nashville),! (bbq)-[:ONLY_IN]->(memphis),! (jk)-[:LOVES]->(hotchicken),! ! // . . . snip . . .!
  • 30. Create Some Relationships // . . . snip . . .! ! CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),! (jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),! (hotchicken)-[:ONLY_IN]->(nashville),! (bbq)-[:ONLY_IN]->(memphis),! (jk)-[:LOVES]->(hotchicken),! ! // . . . snip . . .!
  • 31. Create Some Relationships // . . . snip . . .! ! CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),! (jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),! (hotchicken)-[:ONLY_IN]->(nashville),! (bbq)-[:ONLY_IN]->(memphis),! (jk)-[:LOVES]->(hotchicken),! ! // . . . snip . . .!
  • 32. Create Some Relationships // . . . snip . . .! ! CREATE (jk)-[:WORKS_AT { title: {"CTO"}]->(gs),! (jk)-[:LIVES_IN]->(memphis)-[:LIVED_IN]->(nashville),! (hotchicken)-[:ONLY_IN]->(nashville),! (bbq)-[:ONLY_IN]->(memphis),! (jk)-[:LOVES]->(hotchicken),! ! // . . . snip . . .!
  • 33. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  • 34. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  • 35. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  • 36. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  • 37. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  • 38. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  • 39. Example Cypher Query MATCH (p:Person { name: "Jeremy Kendall" })-[:LOVES]->(l)! WITH p, l! MATCH (p)-[:WORKS_AT]->(j)! WITH p, l, j! MATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)! RETURN p, l, j, o
  • 42. Neo4jPHP • PHP wrapper for the Neo4j REST API • Installable via Composer • Used internally at Graph Story • Used in this presentation • Well tested • https://packagist.org/packages/everyman/ neo4jphp
  • 43. Also see: NeoClient • Written by Neoxygen • Alternative PHP wrapper for the Neo4j REST API • Installable via Composer • Under review for internal use at Graph Story • Well tested • https://packagist.org/packages/neoxygen/neoclient
  • 44. Connecting $neo4jClient = new EverymanNeo4jClient(! ‘yourgraph.example.com’, ! 7473! );! ! $neo4jClient->getTransport()! ->setAuth('username', 'password')! ->getTransport()->useHttps();
  • 45. Creating a Node and Label $node = new Node($neo4jClient);! ! $label = $neo4jClient->makeLabel('Person');! ! $node->setProperty('name', ‘Jeremy Kendall');! ! $node->save()->addLabels(array($label));
  • 46. Searching // Searching for a label by property! $label = $neo4jClient->makeLabel('Person');! $nodes = $label->getNodes('name', $name);
  • 47. Querying (Cypher) $queryString = ! 'MATCH (p:Person { name: { name }}) RETURN p';! ! $query = new EverymanNeo4jCypherQuery(! $neo4jClient,! $queryString,! ['name' => ‘Jeremy Kendall']! );! ! $result = $query->getResultSet();
  • 49. Named Parameters $queryString = ! 'MATCH (p:Person { name: { name }}) RETURN p';! ! $query = new EverymanNeo4jCypherQuery(! $neo4jClient,! $queryString,! ['name' => ‘Jeremy Kendall']! );! ! $result = $query->getResultSet();
  • 50. Named Parameters $queryString = ! 'MATCH (p:Person { name: { name }}) RETURN p';! ! $query = new EverymanNeo4jCypherQuery(! $neo4jClient,! $queryString,! ['name' => ‘Jeremy Kendall']! );! ! $result = $query->getResultSet();
  • 52. News Feed • Modeled as a list of posts • Newest post first • All subsequent posts follow • Relationships: LASTPOST and NEXTPOST
  • 55. The Content Model class Content! {! public $node;! public $nodeId;! public $contentId;! public $title;! public $url;! public $tagstr;! public $timestamp;! public $userNameForPost;! public $owner = false;! }
  • 56. Adding Content public static function add($username, Content $content)! {! $queryString =<<<CYPHER! MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner! CYPHER;! ! $query = new Query(! Neo4jClient::client(),! $queryString,! array(! 'u' => $username,! 'title' => $content->title,! 'url' => $content->url,! 'tagstr' => $content->tagstr,! 'timestamp' => time(),! 'contentId' => uniqid()! )! );! $result = $query->getResultSet();! ! return self::returnMappedContent($result);! }
  • 57. Adding Content public static function add($username, Content $content)! {! $queryString =<<<CYPHER! MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner! CYPHER;! ! $query = new Query(! Neo4jClient::client(),! $queryString,! array(! 'u' => $username,! 'title' => $content->title,! 'url' => $content->url,! 'tagstr' => $content->tagstr,! 'timestamp' => time(),! 'contentId' => uniqid()! )! );! $result = $query->getResultSet();! ! return self::returnMappedContent($result);! }
  • 58. Adding Content public static function add($username, Content $content)! {! $queryString =<<<CYPHER! MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner! CYPHER;! ! $query = new Query(! Neo4jClient::client(),! $queryString,! array(! 'u' => $username,! 'title' => $content->title,! 'url' => $content->url,! 'tagstr' => $content->tagstr,! 'timestamp' => time(),! 'contentId' => uniqid()! )! );! $result = $query->getResultSet();! ! return self::returnMappedContent($result);! }
  • 59. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  • 60. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  • 61. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  • 62. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  • 63. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  • 64. Adding Content MATCH (user { username: {u}})! OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)! DELETE r! CREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url: {url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId: {contentId} })! WITH p, collect(lastpost) as lastposts! FOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)! RETURN p, {u} as username, true as owner
  • 65. Adding Content $query = new Query(! $neo4jClient,! $queryString,! array(! 'u' => $username,! 'title' => $content->title,! 'url' => $content->url,! 'tagstr' => $content->tagstr,! 'timestamp' => time(),! 'contentId' => uniqid()! )! );! ! $result = $query->getResultSet();
  • 66. Retrieving Content public static function getContent($username, $skip)! {! $queryString = <<<CYPHER! MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4! CYPHER;! ! $query = new Query(! Neo4jClient::client(),! $queryString,! array(! 'u' => $username,! 'skip' => $skip,! )! );! ! $result = $query->getResultSet();! ! return self::returnMappedContent($result);! }
  • 67. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  • 68. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  • 69. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  • 70. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  • 71. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  • 72. Retrieving Content MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->f! WITH DISTINCT f, u! MATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-p! RETURN p, f.username as username, f = u as owner! ORDER BY p.timestamp desc SKIP { skip } LIMIT 4
  • 73. Editing Content public static function edit(Content $content)! {! $updatedAt = time();! ! $node = $content->node;! $node->setProperty('title', $content->title);! $node->setProperty('url', $content->url);! $node->setProperty('tagstr', $content->tagstr);! $node->setProperty('updated', $updatedAt);! $node->save();! ! $content->updated = $updatedAt;! ! return $content;! }
  • 74. Editing Content public static function edit(Content $content)! {! $updatedAt = time();! ! $node = $content->node;! $node->setProperty('title', $content->title);! $node->setProperty('url', $content->url);! $node->setProperty('tagstr', $content->tagstr);! $node->setProperty('updated', $updatedAt);! $node->save();! ! $content->updated = $updatedAt;! ! return $content;! }
  • 75. Editing Content public static function edit(Content $content)! {! $updatedAt = time();! ! $node = $content->node;! $node->setProperty('title', $content->title);! $node->setProperty('url', $content->url);! $node->setProperty('tagstr', $content->tagstr);! $node->setProperty('updated', $updatedAt);! $node->save();! ! $content->updated = $updatedAt;! ! return $content;! }
  • 76. Editing Content public static function edit(Content $content)! {! $updatedAt = time();! ! $node = $content->node;! $node->setProperty('title', $content->title);! $node->setProperty('url', $content->url);! $node->setProperty('tagstr', $content->tagstr);! $node->setProperty('updated', $updatedAt);! $node->save();! ! $content->updated = $updatedAt;! ! return $content;! }
  • 77. Editing Content public static function edit(Content $content)! {! $updatedAt = time();! ! $node = $content->node;! $node->setProperty('title', $content->title);! $node->setProperty('url', $content->url);! $node->setProperty('tagstr', $content->tagstr);! $node->setProperty('updated', $updatedAt);! $node->save();! ! $content->updated = $updatedAt;! ! return $content;! }
  • 78. Deleting Content public static function delete($username, $contentId)! {! $queryString = self::getDeleteQueryString(! $username, ! $contentId! );! ! $params = array(! 'username' => $username,! 'contentId' => $contentId,! );! ! $query = new Query(! $neo4jClient,! $queryString, ! $params! );! $query->getResultSet();! }
  • 79. Deleting Content public static function delete($username, $contentId)! {! $queryString = self::getDeleteQueryString(! $username, ! $contentId! );! ! $params = array(! 'username' => $username,! 'contentId' => $contentId,! );! ! $query = new Query(! $neo4jClient,! $queryString, ! $params! );! $query->getResultSet();! }
  • 80. Deleting Content public static function delete($username, $contentId)! {! $queryString = self::getDeleteQueryString(! $username, ! $contentId! );! ! $params = array(! 'username' => $username,! 'contentId' => $contentId,! );! ! $query = new Query(! $neo4jClient,! $queryString, ! $params! );! $query->getResultSet();! }
  • 81. Deleting Content public static function delete($username, $contentId)! {! $queryString = self::getDeleteQueryString(! $username, ! $contentId! );! ! $params = array(! 'username' => $username,! 'contentId' => $contentId,! );! ! $query = new Query(! $neo4jClient,! $queryString, ! $params! );! $query->getResultSet();! }
  • 82. Deleting Content: Leaf // If leaf! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(c:Content { contentId: { contentId }})! WITH c! MATCH (c)-[r]-()! DELETE c, r
  • 83. Deleting Content: Leaf // If leaf! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(c:Content { contentId: { contentId }})! WITH c! MATCH (c)-[r]-()! DELETE c, r
  • 84. Deleting Content: Leaf // If leaf! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(c:Content { contentId: { contentId }})! WITH c! MATCH (c)-[r]-()! DELETE c, r
  • 85. Deleting Content: Leaf // If leaf! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(c:Content { contentId: { contentId }})! WITH c! MATCH (c)-[r]-()! DELETE c, r
  • 86. Deleting Content: Leaf // If leaf! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(c:Content { contentId: { contentId }})! WITH c! MATCH (c)-[r]-()! DELETE c, r
  • 87. Deleting Content: LASTPOST // If last! MATCH (u:User { username: { username }})-[lp:LASTPOST]- >(del:Content { contentId: { contentId }})-[np:NEXTPOST]- >(nextPost)! CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)! DELETE lp, del, np
  • 88. Deleting Content: LASTPOST // If last! MATCH (u:User { username: { username }})-[lp:LASTPOST]- >(del:Content { contentId: { contentId }})-[np:NEXTPOST]- >(nextPost)! CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)! DELETE lp, del, np
  • 89. Deleting Content: LASTPOST // If last! MATCH (u:User { username: { username }})-[lp:LASTPOST]- >(del:Content { contentId: { contentId }})-[np:NEXTPOST]- >(nextPost)! CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)! DELETE lp, del, np
  • 90. Deleting Content: LASTPOST // If last! MATCH (u:User { username: { username }})-[lp:LASTPOST]- >(del:Content { contentId: { contentId }})-[np:NEXTPOST]- >(nextPost)! CREATE UNIQUE (u)-[:LASTPOST]->(nextPost)! DELETE lp, del, np
  • 91. Deleting Content: Other // All other! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(before),! (before)-[delBefore]->(del:Content { contentId: { contentId }})-[delAfter]->(after)! CREATE UNIQUE (before)-[:NEXTPOST]->(after)! DELETE del, delBefore, delAfter
  • 92. Deleting Content: Other // All other! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(before),! (before)-[delBefore]->(del:Content { contentId: { contentId }})-[delAfter]->(after)! CREATE UNIQUE (before)-[:NEXTPOST]->(after)! DELETE del, delBefore, delAfter
  • 93. Deleting Content: Other // All other! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(before),! (before)-[delBefore]->(del:Content { contentId: { contentId }})-[delAfter]->(after)! CREATE UNIQUE (before)-[:NEXTPOST]->(after)! DELETE del, delBefore, delAfter
  • 94. Deleting Content: Other // All other! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(before),! (before)-[delBefore]->(del:Content { contentId: { contentId }})-[delAfter]->(after)! CREATE UNIQUE (before)-[:NEXTPOST]->(after)! DELETE del, delBefore, delAfter
  • 95. Deleting Content: Other // All other! MATCH (u:User { username: { username }})-[:LASTPOST| NEXTPOST*0..]->(before),! (before)-[delBefore]->(del:Content { contentId: { contentId }})-[delAfter]->(after)! CREATE UNIQUE (before)-[:NEXTPOST]->(after)! DELETE del, delBefore, delAfter