高效的用户时间线在PHP应用程序与Neo4j
现在你遇到的任何社交应用程序都有一个时间轴,显示你的朋友或追随者的状态,通常是按时间降序排列的。在普通SQL或NoSQL数据库中实现这样的特性从来都不是一件容易的事情。
查询的复杂性,随着好友/粉丝数量的增加而增加的性能影响,以及发展社交模式的难度,这些都是图形数据库正在消除的问题。
在本教程中,我们将分别扩展Neo4j和PHP的两篇介绍文章中使用的演示应用程序:
该应用程序构建在Silex上,用户可以跟随其他用户。本文的目标是高效地建模提要的特性,以便检索您关注的人的最后两个帖子,并按时间对它们进行排序。
您将发现一种特殊的建模技术,称为链表和一些高级查询Cypher。
本文的源代码可以在<一个href="https://github.com/ikwattro/social-timeline">它自己的Github仓库一个>.
在图形数据库中建模时间轴
习惯于其他数据库建模技术的人倾向于将每个帖子与用户联系起来。一个帖子将有一个时间戳属性,帖子的顺序将根据这个属性来执行。
这是一个简单的表示:
虽然这样的模型工作没有任何问题,但也有一些缺点:
- 对于每个用户,您需要按时间对他的帖子进行排序,以获得最后一篇
- 订单操作将随着您关注的帖子和用户数量线性增长
- 它迫使数据库执行排序操作
利用图形数据库的强大功能
图数据库中的节点保存对其拥有的连接的引用,为图遍历提供了快速性能。
一种常见的用户提要建模技术被称为链表.在我们的应用程序中,用户节点将有一个名为LAST_POST到用户创建的最后一个帖子。这篇文章将有一个PREVIOUS_POST与前一个的关系,它也有PREVIOUS_POST到之前的第二篇文章,等等……
使用这种模式,您可以立即访问用户的最新帖子。事实上,您甚至根本不需要时间戳来检索它的时间轴(但是我们将保留它,以便对不同用户的帖子进行排序)。
更重要的是,用户在时间上所做的事情在图形数据库中以自然的方式建模。能够以一种与这些数据在数据库之外的生活方式相对应的方式存储数据,对于分析、查找和理解数据是一个真正的好处。
初始设置
我建议您下载用于介绍文章的存储库,并将其重命名为social-timeline例如:
git克隆git@github.com: sit必威西盟体育网页登录epoint-editors /社交网络mv社交网络social-timelinecdsocial-timelinermrf .作曲家安装鲍尔安装
与前几篇文章一样,我们将使用生成的虚拟数据集在<一个href="http://graphgen.neoxygen.io">Graphgen一个>.
您需要有一个正在运行的数据库(本地或远程),请转到此<一个href="http://graphgen.neoxygen.io/?graph=xyiag">链接一个>,点击Generate,然后点击“Populate your database”。
如果使用Neo4j 2.2,则需要提供neo4j
graphgen填充器框中的用户名和密码:
这将导入50个用户,包括登录名和姓氏。每个用户将有两篇博文,其中一篇带有必威滚LAST_POST与用户的关系和具有PREVIOUS_POST与其他提要的关系。
如果您现在打开Neo4j浏览器,您可以看到用户和帖子是如何建模的:
显示用户提要
应用程序已经有一组控制器和模板。你可以选择一个用户点击他们,它将显示他们的关注者和一些建议的人关注。
用户馈送路由
首先,我们将添加一个路由,用于显示特定用户的提要。的末尾添加这部分代码web / index . php
文件
美元的应用->得到(/用户/{用户_}/职位的,“Ikwattro \ \社交网络控制器\ \ \ \ WebController:: showUserPosts”)->绑定(“user_post”);
用户输入控制器和Cypher查询
中映射到动作的路径src /控制器/ WebController.php
文件。
在此操作中,我们将从Neo4j数据库获取给定用户的提要,并将它们与用户节点一起传递给模板。
公共函数showUserPosts(应用程序美元的应用程序,请求美元的请求){美元的登录=美元的请求->得到(“用户_”);neo美元=美元的应用程序[“新”];美元的查询='MATCH (user: user) WHERE用户。登录={登录}匹配(user)-[:LAST_POST]->(latest_post)-[PREVIOUS_POST*0..2]->(post) RETURN user, collect(post) as posts';美元的参数=[“登录”= >美元的登录];美元的结果=neo美元->sendCypherQuery(美元的查询,美元的参数)->getResult();如果(零= = =美元的结果->得到(“用户”)){美元的应用程序->中止(404,'用户$login未找到');}美元的帖子=美元的结果->得到(“文章”);返回美元的应用程序[“树枝”]->渲染(“show_user_posts.html.twig”,数组(“用户”= >美元的结果->getSingle(“用户”),“文章”= >美元的帖子,));}
一些解释:
- 我们第一次
匹配
用户的登录名。 - 然后,我们
匹配
的最后一个提要,并展开到PREVIOUS_FEED(使用* 0 . . 2
关系深度将影响将latest_post节点嵌入到post节点集合中),我们将最大深度限制为2。 - 我们在集合中返回找到的提要。
在模板中显示提要
我们将首先在用户配置文件中添加一个链接来访问他们的提要,只需在用户信息块的末尾添加这一行:
<p><一个href="{{路径('user_post', {user_login: user.property('登录')})}}">显示文章一个>p>
现在我们将创建显示用户时间轴(帖子)的模板。我们设置了一个标题和一个循环迭代我们的feed集合,以便在专用的html div中显示它们:
{%扩展“layout.html”。树枝" %}{%块内容%}<h1>{{user.property('login')}}h1>{% for post in posts %}<div类="行"><h4>{{post.properties.title}}h4><div>{{post.properties.body}}div>div><人力资源/>{% endfor %} {% endblock %}
如果您现在选择一个用户并单击显示用户帖子链接,您可以看到我们的帖子被很好地显示,并按降序时间排序,而不指定日期属性。
显示时间轴
如果您已经使用Graphgen导入了示例数据集,那么您的每个用户将跟随大约40个其他用户。
要显示用户时间轴,您需要获取他所关注的所有用户,并将查询扩展到LAST_POST
每个用户的关系。
当您获得所有这些帖子时,您需要按时间对它们进行过滤,以便在用户之间排序。
用户时间线路由
过程与前一个相同-我们将路由添加到index . php
,我们创建我们的控制器动作,我们在用户配置文件模板中添加一个到时间轴的链接,我们创建我们的用户时间轴模板。
将路由添加到web / index . php
文件
美元的应用->得到(“/ user_timeline /{用户_}”,“Ikwattro \ \社交网络控制器\ \ \ \ WebController:: showUserTimeline”)->绑定(“user_timeline”);
控制器动作:
公共函数showUserTimeline(应用程序美元的应用程序,请求美元的请求){美元的登录=美元的请求->得到(“用户_”);neo美元=美元的应用程序[“新”];美元的查询='MATCH (user: user) WHERE用户。登录={用户_}匹配(user)-[:FOLLOWS]->(friend)-[:LAST_POST]->(latest_post)-[:PREVIOUS_POST*0..2]->(post) WITH user, friend, post ORDER BY post.timestamp DESC SKIP 0 LIMIT 20 RETURN user, collect({friend: friend, post: post}) as timeline';美元的参数=[“用户_”= >美元的登录];美元的结果=neo美元->sendCypherQuery(美元的查询,美元的参数)->getResult();如果(零= = =美元的结果->得到(“用户”)){美元的应用程序->中止(404,'用户$login未找到');}$ user=美元的结果->getSingle(“用户”);美元的时间表=美元的结果->得到(“时间轴”);返回美元的应用程序[“树枝”]->渲染(“show_timeline.html.twig”,数组(“用户”= >美元的结果->得到(“用户”),“时间轴”= >美元的时间表,));}
查询说明:
- 首先我们匹配我们的用户。
- 然后我们匹配这个用户、他所关注的其他用户和他们的最后一个提要之间的路径(这里可以看到Cypher是如何真正表达您想要检索的内容的)。
- 我们根据时间戳对提要进行排序。
- 我们返回包含作者和提要的集合中的提要。
- 我们将结果限制为20个提要。
给用户配置文件模板添加一个链接,就在用户输入链接之后:
<p><一个href="{{路径('user_timeline', {user_login: user.property('登录')})}}">显示时间一个>p>
并创建时间轴模板:
%扩展“layout.html”。树枝" %}{%块内容%}<h1>{{user.property('login')}}的时间轴h1>{% for friendFeed in timeline %}<div类="行"><h4>{{friendFeed.post.title}}h4><div>{{friendFeed.post.body}}div><p>编写:{{friendFeed.friend.login}} on {{friendFeed.post.timestamp | date('Y-m-d H:i:s')}}p>div><人力资源/>{% endfor %} {% endblock %}
我们现在有了一个很酷的时间轴,显示了你关注的人的最近20个feed,这对数据库来说是有效的。
添加一个帖子到时间轴
为了将帖子添加到链表中,Cypher查询有点复杂棘手的.您需要创建post节点,删除LAST_POST从用户到旧的latest_post的关系,创建最后一个post节点和用户之间的新关系,最后创建PREVIOUS_POST新旧后节点之间的关系。
很简单,不是吗?我们走吧!
像往常一样,我们将为指向WebController动作的表单创建POST路由:
美元的应用->帖子(' / new_post ',“Ikwattro \ \社交网络控制器\ \ \ \ WebController::岗位”)->绑定(“new_post”);
接下来,我们将添加一个基本的HTML表单,用于在用户模板中插入文章标题和文本:
# show_user.html.twig<div类="行"><div类="col-sm-6"><h5>添加用户状态h5><形式id="new_post"方法="帖子"行动="{{路径('new_post')}}"><div类="形式的班级"><标签为="form_post_title">文章标题:标签><输入类型="文本"最小长度="3."的名字="post_title"id="form_post_title"类="表单控件"/>div><div类="形式的班级"><标签为="form_post_body">帖子文本:标签><文本区域的名字="post_body"类="表单控件">文本区域>div><输入类型="隐藏的"的名字="用户_"价值="{{user.property ('登录')}}"/><按钮类型="提交"类="btn btn-success">提交按钮>形式>div>div>
最后,我们创建岗位行动:
公共函数岗位(应用程序美元的应用程序,请求美元的请求){美元的标题=美元的请求->得到(“post_title”);美元的身体=美元的请求->得到(“post_body”);美元的登录=美元的请求->得到(“用户_”);美元的查询='MATCH (user: user) WHERE用户。登录={用户_}OPTIONAL MATCH (user)-[r:LAST_POST]->(oldPost) DELETE r CREATE (p:Post) SET p.title = {post_title}, p.body = {post_body} CREATE (user)-[:LAST_POST]->(p) WITH p, collect(oldPost) as oldLatestPosts FOREACH (x in oldLatestPosts|CREATE (p)-[:PREVIOUS_POST]->(x)) RETURN p';美元的参数=[“用户_”= >美元的登录,“post_title”= >美元的标题,“post_body”= >美元的身体];美元的结果=美元的应用程序[“新”]->sendCypherQuery(美元的查询,美元的参数)->getResult();如果(零= = !美元的结果->getSingle(“p”)){redirectRoute美元=美元的应用程序[“url_generator”]->生成(“user_post”,数组(“用户_”= >美元的登录));返回美元的应用程序->重定向(redirectRoute美元);}美元的应用程序->中止(500,sprintf(“为用户“%s”插入新帖子时出现问题”,美元的登录));}
一些解释:
- 我们首先匹配用户,然后选择性地匹配他的用户LAST_POST节点。
- 我们删除用户和他最近的最后一篇文章之间的关系。
- 我们创建了我们的新帖子(这实际上是他在现实生活中的最后一个帖子)。
- 我们创建用户和他的“新”上一篇帖子之间的关系。
- 我们中断查询并传递给用户,最后的帖子和他以前的latest_posts的集合。
- 然后遍历集合并创建PREVIOUS_POST上一篇新文章和下一篇文章的关系。
这里棘手的部分是oldLatestPosts集合将始终包含0或1个元素,这对于我们的查询是理想的。
结论
在本文中,我们发现了一种称为链表的建模技术,学习了如何在社交应用程序中实现该技术,以及如何以有效的方式检索节点和关系。我们还学习了一些新的Cypher子句,如SKIP和LIMIT,用于分页。
虽然真实世界的时间轴比我们在这里看到的要复杂得多,但我希望像Neo4j这样的图形数据库确实是这类应用程序的最佳选择。