How To Read Twitter Stream With PHP and Laravel

How To Read Twitter Stream With PHP and Laravel

A couple of days ago we came across a cool feature from Microsoft Edge that lets you report browser bugs via Twitter.

It works like this, all of the messages that contain the hashtag #EdgeBug would open a new issue. Pretty simple right? How hard would it be to write something like that for your next GitHub project? Seems it's easier than you thought.

This tutorial will consist of two parts. The first part will teach you how to listen to a Twitter stream that contains a certain keyword and in the next tutorial we'll show you how easy it is to interact with Github.

Ok, you'll need guzzle for this, but you probably already have it installed. In case you don't, here's how you install the requirements.

composer require guzzlehttp/guzzle and composer require guzzlehttp/oauth-subscriber

There you go, now let's start with creating a command that will watch for a certain keyword and print it out. In the second part of the tutorial, instead of printing it out, we'll open a GitHub issue.

php artisan make:console WatchTwitterStream

And here's what goes into the command

<?php

namespace App\Console\Commands;

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Subscriber\Oauth\Oauth1;
use Illuminate\Console\Command;

class WatchTwitterStream extends Command
{
    private $endpoint = "https://stream.twitter.com/1.1/";

    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'twitter:watch';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Watch twitter for a keyword';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $keyword = "#EdgeBug";

        $this->info("Watching twitter messages that contain {$keyword}");

        $body = $this->watchTwitterFor($keyword);

        while (!$body->eof()) {
            $tweet = json_decode($this->readLine($body), true);
            echo $tweet['text'] . PHP_EOL;
        }
    }

    public function watchTwitterFor($watchFor)
    {
        $defaults = [];

        $stack = HandlerStack::create();
        $oauth = new Oauth1([
            'consumer_key'    => 'consumer_key_from',
            'consumer_secret' => 'consumer_secret_from',
            'token'           => 'token',
            'token_secret'    => 'token_secret',
        ]);
        $stack->push($oauth);
        $defaults = array_merge($defaults, [
            'base_uri' => $this->endpoint,
            'handler'  => $stack,
            'auth'     => 'oauth',
            'stream'   => true,
        ]);
        $this->client = new Client($defaults);

        $response = $this->client->post('statuses/filter.json', [
            'form_params' => [
                'track' => $watchFor,
            ],
        ]);

        return $response->getBody();
    }

    public function readLine($stream, $maxLength = null, $eol = PHP_EOL)
    {
        $buffer    = '';
        $size      = 0;
        $negEolLen = -strlen($eol);
        while (!$stream->eof()) {
            if (false === ($byte = $stream->read(1))) {
                return $buffer;
            }
            $buffer .= $byte;
            // Break when a new line is found or the max length - 1 is reached
            if (++$size == $maxLength || substr($buffer, $negEolLen) === $eol) {
                break;
            }
        }
        return $buffer;
    }
}

In order to consume the twitter streaming API, you'll need some consumer keys which you can get from https://apps.twitter.com/ Not much is going on here. We're using Guzzle to connect to the Twitter stream and once a tweet shows up that contains our keyword, we simply print it out for now.

while (!$body->eof()) {
    $tweet = json_decode($this->readLine($body), true);
    echo $tweet['text'] . PHP_EOL;
}

As you can see this is an endless loop because Twitter stream is an endless supply of data which we need to process.

In the readLine method we just use the stream object and fetch the contents as you can see here:

public function readLine($stream, $maxLength = null, $eol = PHP_EOL)
{
    $buffer    = '';
    $size      = 0;
    $negEolLen = -strlen($eol);
    while (!$stream->eof()) {
        if (false === ($byte = $stream->read(1))) {
            return $buffer;
        }
        $buffer .= $byte;
        // Break when a new line is found or the max length - 1 is reached
        if (++$size == $maxLength || substr($buffer, $negEolLen) === $eol) {
            break;
        }
    }
    return $buffer;
}

The `watchTwitterFor() method connects to Twitter and creates a POST request using Guzzle telling Twitter what we want to look for ie. our keyword.

public function watchTwitterFor($watchFor)
{
    $defaults = [];

    $stack = HandlerStack::create();
    $oauth = new Oauth1([
        'consumer_key'    => 'consumer_key_from',
        'consumer_secret' => 'consumer_secret_from',
        'token'           => 'token',
        'token_secret'    => 'token_secret',
    ]);
    $stack->push($oauth);
    $defaults = array_merge($defaults, [
        'base_uri' => $this->endpoint,
        'handler'  => $stack,
        'auth'     => 'oauth',
        'stream'   => true,
    ]);
    $this->client = new Client($defaults);

    $response = $this->client->post('statuses/filter.json', [
        'form_params' => [
            'track' => $watchFor,
        ],
    ]);

    return $response->getBody();
}

This tutorial is very simple but it shows how easy it is to consume Twitter stream and act on it. In the next tutorial we'll cover how to create a Github issue if you "catch" a Tweet and in no time your clients will be able to open issues for you via Twitter, and you must admit that's something you have always wanted ;)



comments powered by Disqus