Lec_6 Manipulating MySQL Databases with PHP PHP Programming with MySQL.
Leveraging the Power of Graph Databases in PHP
-
Upload
jeremy-kendall -
Category
Technology
-
view
206 -
download
1
Transcript of Leveraging the Power of Graph Databases in PHP
Graphs != Charts
https://www.flickr.com/photos/markgroves/3065192499/
Graphs != Charts
http://stephenwildish.tumblr.com/post/101408321763/friday-project-witch-moral-compass
Graph Databases• Data Model
• Nodes with properties
• Typed relationships
• Strengths
• Highly connected data
• ACID
• Weaknesses
• Paradigm shift
• Examples
• Neo4j, Titan, OrientDB
Why Care?
• Relationships have 1st class status
• Just as important as the objects they connect
• You can have properties & labels
• Multiple relationships
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 SpeedDepth 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
Cypher
• Neo4j’s declarative query language
• Easy to pick up
• Some clauses and concepts familiar from SQL
Create Some NodesCREATE (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, lMATCH (p)-[:WORKS_AT]->(j)WITH p, l, jMATCH (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, lMATCH (p)-[:WORKS_AT]->(j)WITH p, l, jMATCH (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, lMATCH (p)-[:WORKS_AT]->(j)WITH p, l, jMATCH (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, lMATCH (p)-[:WORKS_AT]->(j)WITH p, l, jMATCH (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, lMATCH (p)-[:WORKS_AT]->(j)WITH p, l, jMATCH (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, lMATCH (p)-[:WORKS_AT]->(j)WITH p, l, jMATCH (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, lMATCH (p)-[:WORKS_AT]->(j)WITH p, l, jMATCH (p)-[:LIVES_IN]->(c:City)-[:LIVED_IN*0..]->(o:City)RETURN p, l, j, o
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
• Accepted for internal use at Graph Story
• Well tested
• https://packagist.org/packages/neoxygen/neoclient
Connecting$neo4jClient = new \Everyman\Neo4j\Client( ‘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 \Everyman\Neo4j\Cypher\Query( $neo4jClient, $queryString, ['name' => ‘Jeremy Kendall']);
$result = $query->getResultSet();
Named Parameters
$queryString = 'MATCH (p:Person { name: { name }}) RETURN p';
$query = new \Everyman\Neo4j\Cypher\Query( $neo4jClient, $queryString, ['name' => ‘Jeremy Kendall']);
$result = $query->getResultSet();
Named Parameters
$queryString = 'MATCH (p:Person { name: { name }}) RETURN p';
$query = new \Everyman\Neo4j\Cypher\Query( $neo4jClient, $queryString, ['name' => ‘Jeremy Kendall']);
$result = $query->getResultSet();
Content Modeling: News Feeds
Graph Kit for PHP https://github.com/GraphStory/graph-kit-php
News Feed
• Modeled as a list of posts
• Newest post first
• All subsequent posts follow
• Relationships: LASTPOST and NEXTPOST
The Content Modelclass Content{ public $node; public $nodeId; public $contentId; public $title; public $url; public $tagstr; public $timestamp; public $userNameForPost; public $owner = false;}
Adding Contentpublic static function add($username, Content $content){ $queryString =<<<CYPHERMATCH (user { username: {u}})OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)DELETE rCREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:{contentId} })WITH p, collect(lastpost) as lastpostsFOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)RETURN p, {u} as username, true as ownerCYPHER;
$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 Contentpublic static function add($username, Content $content){ $queryString =<<<CYPHERMATCH (user { username: {u}})OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)DELETE rCREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:{contentId} })WITH p, collect(lastpost) as lastpostsFOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)RETURN p, {u} as username, true as ownerCYPHER;
$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 Contentpublic static function add($username, Content $content){ $queryString =<<<CYPHERMATCH (user { username: {u}})OPTIONAL MATCH (user)-[r:LASTPOST]->(lastpost)DELETE rCREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:{contentId} })WITH p, collect(lastpost) as lastpostsFOREACH (x IN lastposts | CREATE p-[:NEXTPOST]->x)RETURN p, {u} as username, true as ownerCYPHER;
$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 rCREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:{contentId} })WITH p, collect(lastpost) as lastpostsFOREACH (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 rCREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:{contentId} })WITH p, collect(lastpost) as lastpostsFOREACH (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 rCREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:{contentId} })WITH p, collect(lastpost) as lastpostsFOREACH (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 rCREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:{contentId} })WITH p, collect(lastpost) as lastpostsFOREACH (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 rCREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:{contentId} })WITH p, collect(lastpost) as lastpostsFOREACH (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 rCREATE (user)-[:LASTPOST]->(p:Content { title:{title}, url:{url}, tagstr:{tagstr}, timestamp:{timestamp}, contentId:{contentId} })WITH p, collect(lastpost) as lastpostsFOREACH (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 Contentpublic static function getContent($username, $skip){ $queryString = <<<CYPHERMATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->fWITH DISTINCT f, uMATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-pRETURN p, f.username as username, f = u as ownerORDER BY p.timestamp desc SKIP { skip } LIMIT 4CYPHER;
$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]->fWITH DISTINCT f, uMATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-pRETURN p, f.username as username, f = u as ownerORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Retrieving Content
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->fWITH DISTINCT f, uMATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-pRETURN p, f.username as username, f = u as ownerORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Retrieving Content
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->fWITH DISTINCT f, uMATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-pRETURN p, f.username as username, f = u as ownerORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Retrieving Content
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->fWITH DISTINCT f, uMATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-pRETURN p, f.username as username, f = u as ownerORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Retrieving Content
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->fWITH DISTINCT f, uMATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-pRETURN p, f.username as username, f = u as ownerORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Retrieving Content
MATCH (u:User { username: { u }})-[:FOLLOWS*0..1]->fWITH DISTINCT f, uMATCH f-[:LASTPOST]-lp-[:NEXTPOST*0..]-pRETURN p, f.username as username, f = u as ownerORDER BY p.timestamp desc SKIP { skip } LIMIT 4
Editing Contentpublic 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 Contentpublic 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 Contentpublic 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 Contentpublic 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 Contentpublic 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 Contentpublic 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 Contentpublic 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 Contentpublic 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 Contentpublic 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 leafMATCH (u:User { username: { username }})-[:LASTPOST|NEXTPOST*0..]->(c:Content { contentId: { contentId }})WITH cMATCH (c)-[r]-()DELETE c, r
Deleting Content: Leaf
// If leafMATCH (u:User { username: { username }})-[:LASTPOST|NEXTPOST*0..]->(c:Content { contentId: { contentId }})WITH cMATCH (c)-[r]-()DELETE c, r
Deleting Content: Leaf
// If leafMATCH (u:User { username: { username }})-[:LASTPOST|NEXTPOST*0..]->(c:Content { contentId: { contentId }})WITH cMATCH (c)-[r]-()DELETE c, r
Deleting Content: Leaf
// If leafMATCH (u:User { username: { username }})-[:LASTPOST|NEXTPOST*0..]->(c:Content { contentId: { contentId }})WITH cMATCH (c)-[r]-()DELETE c, r
Deleting Content: Leaf
// If leafMATCH (u:User { username: { username }})-[:LASTPOST|NEXTPOST*0..]->(c:Content { contentId: { contentId }})WITH cMATCH (c)-[r]-()DELETE c, r
Deleting Content: LASTPOST
// If lastMATCH (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 lastMATCH (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 lastMATCH (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 lastMATCH (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 otherMATCH (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 otherMATCH (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 otherMATCH (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 otherMATCH (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 otherMATCH (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