玩转 ReactPHP

我最近看到了一个 Twitter 墙的实现,用于node.js在 Twitter 上运行搜索并将结果发布到网页上。我一直想使用 ReactPHP 创建一些东西,所以我认为这是一个尝试的好机会。ReactPHP,如果您还没有听说过,它是一个事件驱动的、非阻塞的 I/O,本质上是 node.js 的 PHP 等价物。主要区别在于 ReactPHP 是用纯 PHP 编写的,没有额外的组件,而是node.js不同程序、接口和语言的集合。作为第一次尝试,我想创建一些简单的东西,所以它需要使用简单的 JavaScript 从 ReactPHP 服务器加载给定主题标签的最新推文。我必须警告说,这是 ReactPHP 的一个简单实现,但它显示了如何开始的基础知识。

构建前端

首先要做的是创建一个 HTML 页面来显示推文。创建一个简单的 ReactPHP 服务器时,通常会分配一个端口,在这种情况下,我要做的第一件事就是弄清楚如何在 JavaScript 中向某个端口号(而不是端口 80)发出请求。通常这会违反 JavaScript 强制执行的同源策略,但我们可以使用一种称为 JSONP 的技术来解决这个问题。这涉及一个跨域请求,该请求返回包装在函数回调中的 JSON 字符串。这在 jQuery 中使用该getJSON()函数是可能的。

这个函数的第一个参数是我们 ReactPHP 服务器的完整 URL(包括端口),它必须包含一个类似于 'jsoncallback' 的参数。这是服务器端用于发送回正确数据格式的内容。未使用第二个参数,但它允许您传递其他参数。第三个参数是对返回的数据做一些事情的回调函数。我们将返回一个带有 'content' 属性的 JSON 对象,该对象将包含推文。

function loadtweets() {

    $.getJSON("http://www.tweetstream.local:4000?jsoncallback=?", {},

      function(data) {

        $('#stream').html(data.content);

    }

  );

}

当我们运行此代码时,它将向以下 URL 结构发送请求。

http://www.tweetstream.local:4000?jsoncallback=jQuery18205617587673477829_1353020048954&_=1353020058958

我们在服务器端所做的是返回一个字符串,该字符串将一些 JSON 输出包装在一个与 jsoncallback 参数同名的函数中。函数被扩展,然后它包含的 JSON 可用于返回函数。我们在这里唯一要做的就是将整个内容字符串传递到 ID 为“stream”的元素中。

jQuery18205617587673477829_1353020048954('content':'text')

这个函数基本上包含在对 的调用中setInterval(),它用于每 5 秒轮询一次 ReactPHP 服务器以查看是否有任何推文。我们可以扩展它以尝试将推文保留在屏幕上,并将新推文附加到顶部,但这将适用于本演示的目的。

<div id="stream">

</div>

<script xx_src="jquery-1.8.2.min.js"></script><script>

<!--//--><![CDATA[// ><!--

 

$(document).ready(function() {

 

  // 每 5 秒运行一次 loadtweets() 函数。

  window.setInterval(function() {

    loadtweets();

  }, 5000);

 

  function loadtweets() {

    $.getJSON("http://www.tweetstream.local:4000?jsoncallback=?", {},

      function(data) {

        $('#stream').html(data.content);

      }

    );

  }

});

 

//--><!]]>

</script>

作曲家

有了它,我们现在可以构建我们的 ReactPHP 组件。这是使用名为 Composer 的工具完成的,该工具是 PHP 内置的依赖项管理器。我不会过多地介绍 Composer,但要获取副本,只需在要工作的目录中运行以下命令即可。

curl -s https://getcomposer.org/installer | php

这将下载一个名为 composer.phar 的文件,该文件用于下载各种包。我们需要做的就是告诉 Composer 我们需要下载哪些包。这是使用一个名为composer.jsonwhere we list(以 JSON 格式)我们想要用于该项目的组件的文件来完成的。显然我们需要包含 ReactPHP,但我也包含了 Zend Framework 1,以便我可以使用 Zend_Service_Twitter_Search 类。这是composer.json这个项目的文件。我可以使用不同的 Twitter 类,但我熟悉 Zend 框架的实现,所以为了方便我选择了那个。

{

    "require": {

        "react/http": "0.2.*",

"zendframework/zendframework1": "dev-release-1.12"

    }

}

要让 Composer 做一些事情,只需像这样调用带有标志安装的作曲家文件。

composer install

这将下载 ReactPHP 和 Zend Framework 1 以及一些其他依赖项。您现在将拥有一个名为vendor的目录,其中包含下载的软件包和一个autoloader.php文件。要使用这些目录中的任何内容,只需包含autoload.php文件并开始使用您需要的类。

autoload.php

composer

evenement

guzzle

react

symfony

zendframework

反应PHP

我们终于准备好开始使用 ReactPHP。第一步(除了包含autoload.php文件)是实例化几个对象。这些是事件循环、套接字和 http 对象。ReactPHP 通过创建一个内部事件循环来工作,然后其他系统可以将其插入。然后使用该循环创建一个套接字服务器,然后该服务器又用于创建一个 HTTP 服务器。HTTP 服务器本质上是一个围绕套接字对象的包装器,带有一些额外的东西来处理标头和其他与 HTTP 相关的事情。

然后,HTTP 对象被设置为在它使用该on()方法接收和传入请求时触发一个事件。在这个方法中,我们设置了一个闭包来接收传入的请求并发送相应的输出。这个闭包是我们找到 jsoncallback 包装器名称并返回一个包含我们加载的所有推文的 json 字符串的地方。

最后,我们设置socket server监听4000端口并运行。

<?php

 

require 'vendor/autoload.php';

 

// 加载对象

$loop = React\EventLoop\Factory::create();

$socket = new React\Socket\Server($loop);

$http = new React\Http\Server($socket, $loop);

 

// 设置请求事件

$http-?>on('request', function ($request, $response) use() {

  $query = $request->getQuery();

 

  if (!isset($query['jsoncallback'])) {

    // 没有 jsoncallback 参数通过,所以我们退出。

    $response->writeHead(200, array('Content-Type' => 'text/plain; charset=utf-8'));

    $response->end('finish' . PHP_EOL);

    return;

  }

 

  // 设置正确的标题

  $response->writeHead(200, array(

    'Content-Type' => 'application/x-javascript; charset=utf-8',

    'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',

    'Last-Modified' => gmdate("D, d M Y H:i:s") . ' GMT', 

    'Cache-Control' => 'no-store, no-cache, must-revalidate', 

    'Cache-Control' => 'post-check=0, pre-check=0',

    'Pragma' => 'no-cache',

  ));

 

  // 加载推文

  $searchResults = loadtweets();

 

  // 生成输出

  $output = '';

  if ($searchResults !== FALSE) {

    foreach ($searchResults as $result) {

      $text = clean_tweet($result['text']);

      $output .= '<div>';

      $output .= '<div>' . $result['from_user_name'] . '</div>';

      $output .= '<div><img xx_src="' . $result['profile_image_url'] . '" /></div>';

      $output .= '<div>';

      $output .= '<p>' . $text . ' <span>on ' . $result['created_at'] . '</span></p>';

      $output .= '</div>';

      $output .= '</div>';

    }

  }

 

  // JSON 编码并将输出包装在 jsoncallback 参数中

  $output = $query['jsoncallback'] . '(' . json_encode(array('content' => $output)) . ')' . PHP_EOL;

 

  // 结束响应

  $response->end($output);

});

 

// 侦听套接字 4000

$socket->listen(4000);

 

// 运行服务器

$loop->run();

clean_tweet()上面的函数取自五分钟参数的 Remy Sharp 的 JavaScript Twitter 解析函数的 PHP 实现。这些函数将向用户名和主题标签等内容添加 HTML 链接,这意味着 Twitter 流本身更加用户友好。

当然,我们错过了实际获取推文的重要组成部分。我们使用 Zend_Service_Twitter_Search 类来做到这一点,它是 Zend 框架的一部分。由于我们之前对 Composer 的操作,我们已经可以访问这个类,因此我们不需要包含任何其他内容。这是完整的功能,它将使用标签#drupal 搜索英文推文。

function loadtweets() {

  // 获取推特搜索对象

  $twitterSearch  = new Zend_Service_Twitter_Search('json');

 

  // 为搜索设置正确的语言

  $searchQuery = array(

    'lang' => 'en'

  );

 

  // 运行搜索

  $searchResults  = $twitterSearch->search('#drupal', $searchQuery);

 

  // 返回结果

  return $searchResults;

}

将此脚本保存到文件后,您可以像运行任何其他 PHP 脚本一样运行它。

php TweetStream.php

您可以通过以下命令通过 curl 运行该脚本来检查该脚本是否正在运行。如果没有 jsoncallback 参数,脚本将只返回“已完成”并退出,但这是确保正在侦听正确端口的好方法。

curl www.tweetstream.local:4000

您在脚本中回显的任何内容都将输出到终端而不是等待连接的用户。看到事情按预期进行是一个好主意。要再次停止程序,只需按 Ctrl+c。

我们在这里所做的只是创建一个基于 Web 的脚本,该脚本将拉取最新的推文并将它们返回到浏览器。ReactPHP 的强大之处在于我们可以将搜索推文与请求推文列表的用户分开。我们可以通过使用addPeriodicTimer()事件循环对象的方法创建一个每 10 秒执行一次的计时器来实现这一点。有了这个,我们可以以受控的方式下拉最新的推文,而无需依赖用户交互来运行脚本。

$loop->addPeriodicTimer(10, function() use() {

  gettweets();

});

该gettweets()函数运行 twitter 搜索并将每个结果存储在一个文件中。

function gettweets() {

  // 获取推特搜索对象

  $twitterSearch  = new Zend_Service_Twitter_Search('json');

 

  // 为搜索设置正确的语言

  $searchQuery = array(

    'lang' => 'en'

  );

 

  // 运行搜索

  $searchResults  = $twitterSearch->search('#drupal', $searchQuery);

 

  // 存储结果

  if ($searchResults !== FALSE && count($searchResults['results']) > 0) {

    foreach ($searchResults['results'] as $result) {

      if (!file_exists($cache_directory . $result['id'])) {

    $fp = fopen($cache_directory . $result['id'], 'w+');

    fwrite($fp, serialize($result));

    fclose($fp);

      }

    }

  }

}

然后我们可以更改loadtweets()函数以从保存的文件中提取数据,并在需要时将其返回给请求事件。当我们使用推文的 id 作为文件名时,我们可以对它们进行反向排序,以获得一个排序的推文列表,最后一个排在前面。这意味着搜索推文和向用户显示这些推文现在是两个独立的事件。

function loadtweets() {

  // 获取缓存目录中的文件列表

  $files = array();

  foreach (new DirectoryIterator('cache') as $fileInfo) {

    if ($fileInfo->isDot()) {

      continue;

    }

    $files[] = $fileInfo->getFilename();

  }

 

  // 对我们找到的文件进行反向排序

  rsort($files);

 

  // 解压每个文件

  foreach ($files as $key => $file) {

    $files[$key] = unserialize(file_get_contents('cache/' . $file));

  }

 

  // 返回结果

  return $files;

}

未来的计划

下一步是创建一个套接字服务器并使用 HTML Web 套接字接口或类似 socket.io.js 的接口连接到它。这将不需要时间间隔来提取最新结果。当发现新项目时,它们会被直接推送到浏览器,然后只是推送到列表的顶部,而不是重新加载整个推文流。

在玩这些东西时,我发现有趣的一件事是它所需的范式转变。我在 Web 服务器环境中使用 PHP 已经很长时间了,以至于我一直在考虑处于无状态状态的应用程序。一个主要的启示是,我在全局范围内创建的任何变量或函数都在该范围内持续存在,直到程序停止。这意味着在发出下一个请求时,我在一个请求中设置的任何内容仍然存在。

我可以看到 ReactPHP 的很多潜力,即使只花了几分钟搞乱了简单的套接字和端口。我在这里完成的工作是在几个小时内完成的,尽管我花了一段时间试图弄清楚如何在 JavaScript 中联系 ReactPHP 服务器。查看 ReactPHP 网站以获取更多信息和 ReactPHP 的更多(更好)实现。我目前正在熟悉这个包,将来肯定会再次访问它。

以上是 玩转 ReactPHP 的全部内容, 来源链接: utcz.com/z/338737.html

回到顶部