使用 Atom 格式表达社交对象

小编辑 发布于 2010/07/20 11:09
阅读 437
收藏 1

简介: 社交网站的流行催生了一个新的 Web 订阅源标准,即 Activity Streams,它用于表达人们正在网上做什么。Activity Streams 是 Atom 格式的一个扩展,网站可以使用 Activity Streams 发布社交活动。本文探索 Activity Streams 如何表达社交对象,学习如何用 PHP 构建活动订阅源编码器,发现 Activity Streams 在企业中的一些用途。

在 Web 中,订阅源是机器可读的内容概要,通常是按时间倒序排列的。大多数订阅源一般用于以流行的 RSS 或基于 XML 的 Atom 格式发布博客内容。内容一旦由一个网站发布,就可以被一个用户友好的聚合器读取,或者被网络软件产品进行转换或解析。

从 1999 年开始,联合订阅源就是通过这种方式被消费的。然而,在最近几年,Web 用户正在使用 Facebook、MySpace 和 Twitter 等网站以更加社交化的方式消费内容。这些网站允许人们将其他用户标记为 “朋友”,然后以统一的界面聚集朋友的活动信息。这些网站用户不再订阅单个内容流,他们订阅的是单个的人,希望看到这些人在网上创建、上传和共享的内容。

活动流(activity stream)有时也称为生活日志(lifestream), 是某个人在一个特定网站上的所有活动的信息集合。随着 Web 用户越来越依赖于活动流来获取信息,能够发布和订阅活动流数据变得很重要。但是因为 RSS 和 Atom 不支持社交元数据,所以我们需要一种新的格式来发布社交活动。

Activity Streams 是一个发展中的标准,扩展了 Atom,用于表达社交对象。虽然是一个新的标准,但是它很快成为在 Web 应用程序之间发布活动的事实上的 方法。例如,MySpace、Facebook 和 TypePad 现在都产生 Activity Streams XML 订阅源。但是这一技术并不只适用于消费者 Web 环境。随着企业内部网和内部软件越来越社交化,有充分的商业理由支持将 Activity Streams 实现为一个功能。本文详细阐述了 Activity Streams,列出了它在企业环境中的潜在用途,并举例说明了使用 PHP 解析 Activity Streams 订阅源。

Activity Streams 的起源

Activity Streams 起源于 DiSo Project(参见 参 考资料),这是一个使用为 WordPress 博客平台开发的插件作为起点来构建分散的社交网络的开源项目。在 DiSo 模型中,每个用户简介(profile)都是一个独立的 WordPress 博客,可以宿主在任意互联网连接的基础架构上。然后社交活动就在这些 WordPress 网站的 Internet 之间发生。

联合发布格式是这个方法的重要组成部分。Facebook 等网站是在一个数字平台上服务成百上千的成员用户,与之不同的是,DiSo 假定每一个 WordPress 网站上只有一个用户。因此,查看您朋友在做什么的唯一方法是订阅他们的订阅源,将订阅源解析成内部数据结构,然后在一个聚合界面上查看这些数据。

XML 是实现这个方法的最佳技术,因为它是跨平台的,很容易发布和解析,并且不需要任何专门的技术。DiSo Project 更进一步,发展成了 Activity Streams 标准,即 Atom 订阅源格式的扩展。

Atom 被设计作为 RSS 的替代方法,它可以是:

  • 100% 与供应商无关
  • 由任何人实现
  • 由任何人自由扩展
  • 完全清晰地规范

供应商无关性、可扩展性和清晰的规范对于具前瞻性的标准来说都是非常重要的。Activity Streams 标准要成为分散社交网络的关键要素,也必须坚持这些原则。而且,由于 Atom 的可扩展性,Activity Streams 能够利用现有的应用逻辑和现有的解析器。理论上,如果您已经编写了处理 Atom 的代码(或者使用某种现有的程序库),您只需要额外开发很少的代码。

2009 年 3 月,MySpace 成为第一个以 Activity Streams 格式发布订阅源的主流社交媒体供应商。之后,更多的供应商紧随其后,其中包括 Facebook、Hulu、TypePad 和 Opera。但是 Activity Streams 的应用范围并不仅限于 Facebook 之类的网站。例如,企业内部网可以由于公司内或公司间的社交活动知识而得到大大增强。Activity Streams 格式也使聚合多个流来跟踪公司的交互和在商业社交媒体网站中界定目标成为可能。不管是从管理、分析、算法或是用户角度来看,能够跟踪社交使用信息给新的软 件应用带来了许多机会。

剖析 Activity Streams

Activity Streams 基于 http://activitystrea.ms/spec/1.0/ XML 名称空间在 Atom 上扩展了一些新的架构元素。

一个 Atom 订阅源由一个主元素 feed 组成,其中包括一些初始的元数据(例如,订阅源标题、作者、URL、它引用的全部内容的 URL)。在这个主元素中有若干个 entry 元素,这些元素定义了订阅源的内容项。清单 1 展示了只有一个 entry 元素的示例 Atom 订阅源,它来源于我自己的网站:


清单 1. 来自作者网站的一个示例 Atom 订阅源

				
<?xml version="1.0" encoding="UTF-8"?>
<feed
xmlns="http://www.w3.org/2005/Atom"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xml:lang="en"
xml:base="http://benwerd.com/wp-atom.php"
>
<title type="text">Ben Werdmuller von Elgg</title>
<subtitle type="text"></subtitle>

<updated>2010-05-01T13:13:04Z</updated>

<link rel="alternate" type="text/html" href="http://benwerd.com" />
<id>http://benwerd.com/feed/atom/</id>
<link rel="self" type="application/atom+xml" href="http://benwerd.com
/feed/atom/" />

<entry>
<author>
<name>Ben Werdmuller von Elgg</name>
<uri>http://benwerd.com/</uri>
</author>
<title type="html"><![CDATA[Sample article title]]></title>
<link rel="alternate" type="text/html" href="http://benwerd.com/2010/05
/sample-article-title/" />
<id>http://benwerd.com/2010/05/sample-article-title/</id>
<published>2010-05-01T13:06:22Z</published>
<content type="html" >
...
</content>
</entry>

</feed>

 

Activity Streams 通过在每个 entry 元素中插入描述社交对象的数据,扩展了 Atom 模型。entry 元素可能有的其他子元素不会受到影响。例如,您可以在 titlecontent 子元素中分别包含动作的标题和人类可读的描述,或者在标准的 Atom 格式的 content 元素中包含社交元数据。

Activity Streams entry 元素总是包含三个主元素,它们表示:

  • actor:执行动作的人(例:John Doe
  • verb:执行的动作类型(例:added
  • object:动作所针对的项目(例:Ben's photo

在有些环境中,entry 元素也可能包含 target,这是执行或放置动作的容器对象(例如:动作 John Doe added Ben's photo to John's photo gallery 中的 John's photo gallery)。

entry 元素中使用了下面的元素来定义动词、对象、动作者和目标:

  • Activity:verb 元素包含一个 Atom URI,定义所使用的动词。
  • Activity:object 元素包含一些 activity:object-type 元素(在下一段中介绍),以及描述动作对象所需要的其他元素。
  • 动作者是由现有的 atom:author 元素定义的,如 清单 1 所示。
  • Activity:target 元素类似于 activity:object 元素,但是它专门用于定义它存在的目标。

activity:objectactivity:targetatom:author 元素中有一个或多个 Activity:object-type 元素,它们包含一个定义前辈对象的 Atom URI。

每一种动词和对象都有一个 Atom URI,它定义了它可能包含的属性。例如,清单 2 展示了一些来自 Activity Streams 规范的 Atom URI,它们定义了一个人、共享的动词和博客文章:


清单 2. 定义人、共享和博客文章的 Activity Streams URI

				
http://activitystrea.ms/schema/1.0/person
http://activitystrea.ms/schema/1.0/share
http://activitystrea.ms/schema/1.0/article

 

这些 URI 对应于 Activity Streams 规范中人类可读的项。

清单 3 中,我将 清单 1 中的示例 Atom 订阅源的 entry 元素转换为 Ben Werdmuller 发布的一篇博客文章的 Activity Streams 表示:


清单 3. 用 Activity Streams 格式表示的清单 1 的社交版本

				
<?xml version="1.0" encoding="UTF-8"?>
<feed
xmlns="http://www.w3.org/2005/Atom"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:activity="http://activitystrea.ms/spec/1.0/"
xml:lang="en"
xml:base="http://benwerd.com/wp-atom.php"
>
<title type="text">Ben Werdmuller von Elgg</title>
<subtitle type="text"></subtitle>

<updated>2010-05-01T13:13:04Z</updated>

<link rel="alternate" type="text/html" href="http://benwerd.com" />
<id>http://benwerd.com/feed/atom/</id>
<link rel="self" type="application/atom+xml" href="http://benwerd.com
/feed/atom/" />

<entry>
<id>http://benwerd.com/2010/05/sample-article-title/#actionID</id>
<title type="text">Ben posted a blog entry.</title>
<published>2010-05-01T13:06:22Z</published>
<author>
<name>Ben Werdmuller von Elgg</name>
<uri>http://benwerd.com/</uri>
<activity:object-type>
http://activitystrea.ms/schema/1.0/person
</activity:object-type>
</author>
<activity:verb>
http://activitystrea.ms/schema/1.0/post
</activity:verb>
<activity:object>
<title type="html"><![CDATA[Sample article title]]>
</title>
<link rel="alternate" type="text/html"
href="http://benwerd.com/2010/05/sample-article-title/" />
<id>http://benwerd.com/2010/05/sample-article-title/</id>
<published>2010-05-01T13:06:22Z</published>
<content type="html" >
...
</content>
<activity:object-type>
http://activitystrea.ms/schema/1.0/article
</activity:object-type>
</activity>
</entry>
</feed>

 

这里要注意:

  • 在订阅源顶部添加了名称空间 xmlns:activity=http://activitystrea.ms/spec/1.0/,以便定义 Activity Streams 词汇表。
  • 总 Atom entry 元素的 id 和它包含的 activity:object 元素不能相同。
  • 博客文章详细信息 — 清单 1 中标准 entry 元素的内容 — 现在位于 activity:object 元素中,还有所需要的 activity:object-type 元素。
  • 标准的 atom:titleatom:content 元素被保留用于不支持 Activity Streams 订阅源的解析器。

虽然这个动作只有一个对象,但是可以在 Activity Streams 项中嵌入多个对象 — 例如,修改 清单 3 以表示动作 Ben posted two blog entries。博客可以作为一个整体添加为 activity:target 元素,表示语义上稍微不同的动作 Ben added two entries to his blog

我特意使用 清单 3 订阅源例子中的基本内容类型。然而,Activity Streams 规范允许特殊化动词和对象。例如,一个协作项目管理应用程序会在用户完成任务时提示。软件可能专门定义一个动词表示 complete、一个专门的对象表示 task,各自都有自己的 Atom URI。然而,许多兼容 Activity Streams 的工具可能还不知道有这些概念,这可能会在聚合时造成问题。要减少这种问题,Activity Streams 订阅源中的项可以包含多个描述同一个活动的动词。在这些情况下,最好包含特定动词或对象的整个祖先集。Complete 可能只是基本动词 update 的一个特例,而 Task 有可能是基本对象 note 的一个特例。最终的 Activity Streams 项可能如 清单 4 所示:


清单 4. Activity Streams 订阅源中一个虚假的已完成任务

				
<entry>
<id>http://samplecompany.com/tasks/activity/23432/3242345/</id>
<title type="text">Roger Taylor completed a task.</title>
<published>2010-05-01T13:06:22Z</published>
<author>
<name>Roger Taylor</name>
<uri>http://samplecompany.com/people/Roger+Taylor/</uri>
<activity:object-type>
http://activitystrea.ms/schema/1.0/person
</activity:object-type>
</author>
<activity:verb>
http://samplecompany.com/activity/schema/1.0/complete
</activity:verb>
<activity:verb>
http://activitystrea.ms/schema/1.0/update
</activity:verb>
<activity:object>
<title type="html"><![CDATA[Sample task]]></title>
<link rel="alternate" type="text/html" href="http://samplecompany.com
/tasks/23432/" />
<id>http://samplecompany.com/tasks/23432/</id>
<published>2010-05-01T13:06:22Z</published>
<content type="html" >
...
</content>
<activity:object-type>
http://samplecompany.com/activity/schema/1.0/task
</activity:object-type>
<activity:object-type>
http://activitystrea.ms/schema/1.0/note
</activity:object-type>
</activity>
</entry>

 

这里要注意:

  • 每一个对象类型 URI 都有自己的 activity:object-type 元素。
  • 每一个动词 URI 都有自己的 activity:verb 元素。
  • 专门的对象和动词类型必须有专有的 URI。

在我撰写本文时,Activity Streams Wiki 文档列出了 21 个动词和 25 个对象类型,其中 11 个基本动词和 19 个基本对象类型是定义在正式的规范文档中的。Activity Streams 解析器必须识别每一个基本的动词和对象,并且能够正确处理它们。

构建一个 Activity Streams 编码器

Activity Streams 标准的简单性使得我们很容易构建编码器。这里您将创建一个基本的 PHP 编码器对象,当您将它转换成一个字符串时(例如,通过使用 PHP echo 语句进行处理),它会表示一个 Activity Streams 订阅源。从这里 下 载 完整的源代码。

首先,您需要创建存根类 ActivityStreamEncoder,并定义一个构造函数,参数为 Activity Streams 项的 id、title 和 description。您也需要定义一个私有数组 $entries 用于保存您的 Activity Streams 项。清单 5 展示了这个类及其构造函数。


清单 5. 基本的 ActivityStreamEncoder 类及其构造函数

				
class ActivityStreamsEncoder {

private $id = "";
private $title = "";
private $link = "";
private $entries = array();

function __construct($id, $title = '', $description = '') {
$this->id = $id;
$this->title = $title;
$this->description = $description;
}
}

 

接下来,您应该继续为项、对象和作者添加一些基本类。因为动词一般是由 URI 构成,所以您可以到处将它们作为字符串进行引用。项和对象默认将它们的发布日期设置为当前时间(使用 PHP time() 函数)。清单 6 展示了这些项的基本类:


清单 6. 基本的 ActivityStreamEntry

				
class ActivityStreamsEntry {

public $id = "";
public $title = "";
public $author;
public $objects = array();
public $verbs = array();
public $published;

function __construct() {
$this->published = time();
}

function addObject(ActivityStreamsObject $object) {
$this->objects[] = $object;
}
function addVerb(string $verb) {
$this->verbs[] = $verb;
}
function setAuthor(ActivityStreamsAuthor $author) {
$this->author = $author;
}

}

 

ActivityStreamsEntry 类也包括了用于给项添加作者、对象和动词的简单帮助器方法,可以保证这些值的变量类型是正确的。

因为 activity:object 元素的子元素是变量,所以您需要动态地使用自定义的 getProperty()setProperty() 方法来分别对 ActivityStreamsObject — 您的对象类 — 的属性进行取值和赋值。这一切都定义在 清单 7 中,其中展示了 ActivityStreamsObject 和作者类,后者是前者的一个子类。


清单 7. Activity Streams 对象和作者编码类

				
class ActivityStreamsObject {
public $properties = array();

function getProperty($property_name) {
if (isset($this->properties[$property_name]))
return $this->properties[$property_name];
}
function setProperty($property_name, $property_value) {
$this->properties[$property_name] = $property_value;
}
function __construct() {
$this->published = time();
}

function addObjectType($object_type) {
if (!isset($this->properties['object-type'])) $this->properties
['object-type'] = array();
$this->properties['object-type'][] = (string) $object_type;
}

function __toString() {
$string = '';
$string .= "\n<activity:object>";
foreach($this->properties as $property => $value) {
if (!is_array($value)) $value = array($value);
switch($property) {
case 'title':
$attr = 'type="html"';
break;
case 'link':
$attr = 'rel="alternate" type="text/html"
href="'.$value[0].'"';
$value = array('');
break;
default: $attr = '';
break;
}
if (sizeof($value))
foreach($value as $val) {
if (empty($val))
$string .= "\n\t<{$property} {$attr} />";
else if ($property == 'content' || $property == 'title')
$string .= "\n\t<{$property}
{$attr}><![CDATA[{$val}]]></{$property}>";
else
$string .= "\n\t<{$property} {$attr}>
{$val}</{$property}>";
}
}
$string .= "\n</activity:object>";

return $string;
}

}

class ActivityStreamsAuthor extends ActivityStreamsObject {
function __construct() {
$this->addObjectType("http://activitystrea.ms/schema/1.0/person");
}
function __toString() {
$string = parent::__toString();
$string = str_replace('activity:object>','author>',$string);
return $string;
}
}

 

在这里,您的类分别表示了 activity:objectatom:author 元素的结构化版本。当您尝试将它们转换成一个字符串时(例如,通过将它们输出到浏览器),就会自动返回结构化的 XML。通过保证每个 Activity Streams 元素对应的 PHP 类只负责它自己的 XML 表示,您能够创建一组清晰的 Activity Streams 处理器对象。

清单 8 中,您添加一个类似的类来表示整个 entry 元素:


清单 8. 表示一个 Activity Streams entry 元素的 PHP 类

				
class ActivityStreamsEntry {

public $id = "";
public $title = "";
public $author;
public $objects = array();
public $verbs = array();
public $published;

function __construct() {
$this->published = time();
}

function addObject(ActivityStreamsObject $object) {
$this->objects[] = $object;
}
function addVerb($verb) {
$this->verbs[] = $verb;
}
function setAuthor(ActivityStreamsAuthor $author) {
$this->author = $author;
}

function __toString() {

$string = '';
$string .= "\n<entry>";
$published = date('c',$this->published);
$string .= <<< END
<id>{$this->id}</id>
<title type="text"><![CDATA[{$this->title}]]></title>

<published>{$published}</published>
END;
if ($this->author instanceof ActivityStreamsAuthor) $string .=
$this->author;
if (sizeof($this->verbs)) foreach($this->verbs as $verb)
$string .= "\n<activity:verb>{$verb}</activity:verb>";
if (sizeof($this->objects)) foreach($this->objects as $object)
if ($object instanceof ActivityStreamsObject) $string .= $object;
$string .= "\n</entry>";
return $string;
}
}

 

清单 8 中,这些代码也包含了 addVerb()addObject()setAuthor() 方法,它们分别处理 Activity Streams entry 元素中的 activity:verbactivity:objectatom:author 元素。

最后,您可以修改您的整个 ActivityStreamsEncoder 类,使它包含一个对应的 addEntry() 方法(将 清单 8 中的 ActivityStreamsEntry 对象添加到流中)和一个特别的 __toString() 方法(输出整个 Activity Streams 订阅源)。__toString() 方法包含了正确使用 Activity Streams 词汇表所需要的 XML 头和名称空间声明。清单 9 展示了 addEntry()__toString() 方法:


清单 9. 清单 5 中 ActivityStreamsEncoder 类的 addEntry()__toString() 方法

				
function addEntry(ActivityStreamsEntry $entry) {
$this->entries[] = $entry;
}

function __toString() {
// Display header
$string = '';
$updated_time = date('c',time());
$string .= <<<END
<?xml version="1.0" encoding="UTF-8"?>
<feed
xmlns="http://www.w3.org/2005/Atom"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:activity="http://activitystrea.ms/spec/1.0/"
xml:lang="en"
>

<title type="text">{$this->title}</title>
<updated>{$updated_time}</updated>

<link rel="alternate" type="text/html" href="{$this->link}" />
<id>{$this->id}</id>
<link rel="self" type="application/atom+xml" href="{$this->id}" />

END;
if (sizeof($this->entries))
foreach($this->entries as $entry)
if ($entry instanceof ActivityStreamsEntry) $string .= (string) $entry;

$string .= <<<END
</feed>
END;
return $string;
}

 

这样就完成了一个编码器。现在,您需要连接这些对象,将它们输出到浏览器上,以形成一个结构化的 Activity Streams 订阅源,如 清 单 10 所示:

清单 10. 使用清单 5 到 9 的 PHP 代码呈现清单 4 中的 Activity Streams 订阅源

				
// Create a new Activity Stream
$stream = new ActivityStreamsEncoder('http://samplecompany.com/tasks/activity/',
'Task activities at Sample Company');

// Define the action's object
$object = new ActivityStreamsObject();
$object->setProperty('id','http://samplecompany.com/tasks/23432/');
$object->setProperty('title','Sample task.');
$object->setProperty('content','...');
$object->addObjectType('http://samplecompany.com/activity/schema/1.0/task');
$object->addObjectType('http://activitystrea.ms/schema/1.0/note');
$object->setProperty('link','http://samplecompany.com/tasks/23432/');

// Define the action's author
$author = new ActivityStreamsAuthor();
$author->setProperty('name','Roger Taylor');
$author->setProperty('uri','http://samplecompany.com/people/Roger+Taylor/');

// Define the overall entry, adding the object, verbs and author
$entry = new ActivityStreamsEntry();
$entry->addVerb("http://samplecompany.com/activity/schema/1.0/complete");
$entry->addVerb("http://activitystrea.ms/schema/1.0/update");
$entry->title = "Roger Taylor completed a task.";
$entry->id = 'http://samplecompany.com/tasks/activity/23432/3242345/';
$entry->addObject($object);
$entry->setAuthor($author);

// Add the entry to the stream
$stream->addEntry($entry);

// Echo the stream
header('Content type: text/xml');
echo $stream;

清 单 10 中的代码包含了一个结构化版本的活动流,您可以使用标准的面向对象的 PHP 方法或属性调用进行查询。当调用 echo 显示这个订阅源时,每一个对象内部的 __toString() 都会被调用,从而将订阅源呈现为 XML。Content type: text/xml 头会首先发送给浏览器,使浏览器知道您将显示一个 XML 文件。

解析 Activity Streams 订阅源

PHP 解析 XML 订阅源的方法很简单,就是直接加载具有内置 SimpleXML 函数的结构:

  • Simplexml_load_file($filename) 从文件加载结构化的 XML。
  • Simplexml_load_string($filename) 从字符串加载结构化的 XML。

您的 ActivityStreams 类中的许多修改都是很直观的。其中一个是将您的每一个子类统一成一个多用途的 ActivityStreamsElement 类,它继承了内置的 PHP 类 SimpleXMLElement。然后您可以直接调用 SimpleXML 将 XML 表示的订阅源转换回这些处理器对象:

$activityStream = simplexml_load_string($xmlFeed, "ActivityStreamsElement");

 

但是处理由 SimpleXML 解析函数默认返回的 SimpleXMLElement 结构化序列流就是简单地遍历一个数组的元素,如 清 单 11 所示。然而,您必须注意要同时引用 AtomActivityStreams 名称空间。


清单 11. 遍历一个 SimpleXML Atom 订阅源中的元素

				
$activityStream = simplexml_load_string($stream);
$activityStream = $activityStream->children('http://www.w3.org/2005/Atom');
if (is_array(!$activityStream->entry) && sizeof($activityStream->entry)) {
foreach($activityStream->entry as $entry) {
// Handle entry
}
}

您可以开发递归函数来处理项、项中的对象、作者等等。Activity Streams 订阅源和它们的前辈 Atom 订阅源一样,都是简单的 XML 文件,它们能够被快速地解析,以及用于多种用途。

结束语

Activity Streams 格式是社交网络应用程序的 RSS 替代方法:简单且易于开发,但是在很大的范围内功能强大。在企业环境中,Activity Streams 可以被扩展以实现更安全和更复杂的功能。除了创建专用的对象和动词类型来处理业务活动之外,您还可以使用诸如 OAuth 之类的技术来对流进行签名,从而创建出定制的、基于权限的公司活动视图。而被提议的 Atom Media 扩展允许将多媒体项目(如照片、视频和音频文件、商业展示)嵌入到一个 Activity Streams 订阅源中。

下载本文源代码

加载中
返回顶部
顶部