Building a Twitter datasource

In this recipe we will learn how to implement our own datasource by providing a way to read from and post messages to a Twitter account.

Getting ready

We will integrate this datasource with OAuth, which is an authentication mechanism supported by Twitter. To do so, we will use a class named HttpSocketOauth developed by Neil Crookes, which is an extension to CakePHP's own HttpSocket class that adds OAuth support in a clean and elegant way. Download the file named http_socket_oauth.php from the URL http://github.com/neilcrookes/http_socket_oauth/raw/master/http_socket_oauth.php and place it in your app/vendors folder.

There are other ways to communicate with an OAuth provider such as Twitter, most noticeably using the PHP OAuth library available at http://code.google.com/p/oauth-php. This recipe uses Neil's approach for its simplicity.

Let us continue by creating the Tweet model. Create a file named tweet.php and place it in your app/models folder with the following contents:

<?php
class Tweet extends AppModel {
public $useDbConfig = 'twitter';
}
?>

Create its controller in a file named tweets_controller.php and place it in your app/controllers with the following contents:

<?php
class TweetsController extends AppController {
public function index($twitter) {
$tweets = $this->Tweet->find('all', array(
'conditions' => array('username' => $twitter)
));
$this->set(compact('tweets', 'twitter'));
}
public function add($twitter) {
if (!empty($this->data)) {
$this->Tweet->create();
if ($this->Tweet->save($this->data)) {
$this->Session->setFlash('Succeeded'),
} else {
$this->Session->setFlash('Failed'),
}
}
$this->redirect(array('action'=>'index', $twitter));
}
}
?>

We now need the appropriate view. Create a folder named tweets in your app/views folder, and inside it, create a file named index.ctp with the following contents:

<?php
echo $this->Form->create(array('url' => array('action'=>'add', $twitter)));
echo $this->Form->inputs(array(
'status' => array('label'=>false)
));
echo $this->Form->end('Tweet this'),
?>
<?php foreach($tweets as $tweet) { ?>
<p><?php echo $tweet['Tweet']['text']; ?></p>
<p><small>
<?php echo $this->Html->link(
date('F d, Y', strtotime($tweet['Tweet']['created_at'])),
'http://www.twitter.com/' . $tweet['User']['screen_name'] . '/status/' . $tweet['Tweet']['id']
); ?>
with <?php echo $tweet['Tweet']['source']; ?>
</small></p>
<br />
<?php } ?>

Next, we will need to register our application on Twitter. Go to the URL http://twitter.com/apps/new and fill in the form (an example is shown in the following figure.) Make sure you specify a domain different than localhost when asked for your Application Website, and that you select Read & Write when asked for the Default Access Type. You will also need to specify Browser as the Application Type, and http://localhost/tweets as the Callback URL, replacing localhost with your own host. This callback won't actually be utilized, as we will define it at runtime, but it is mandatory, so we need to fill it in.

Getting ready

When you successfully submit this form, Twitter will give you some information regarding your newly registered application. In that screen, make sure to grab what is shown as Consumer key and Consumer secret, as we will need it when going through this recipe.

Add a new connection named $twitter to your app/config/database.php, by using the following contents and replacing KEY with your Consumer key and SECRET_KEY with the Consumer secret you obtained above:

public $twitter = array(
'datasource' => 'twitter',
'key' => 'KEY',
'secret' => 'SECRET_KEY'
);

How to do it...

We start by fully implementing the datasource. Create a file named twitter_source.php and place it in your app/models/datasources folder with the following contents:

<?php
App::import('Vendor', 'HttpSocketOauth'),
class TwitterSource extends DataSource {
public $_baseConfig = array(
'key' => null,
'secret' => null
);
protected $_schema = array(
'tweets' => array(
'id' => array(
'type' => 'integer',
'null' => true,
'key' => 'primary',
'length' => 11,
),
'text' => array(
'type' => 'string',
'null' => true,
'key' => 'primary',
'length' => 140
),
'status' => array(
'type' => 'string',
'null' => true,
'key' => 'primary',
'length' => 140
),
)
);
public function __construct($config = null, $autoConnect = true) {
parent::__construct($config, $autoConnect);
if ($autoConnect) {
$this->connect();
}
}
public function listSources() {
return array('tweets'),
}
public function describe($model) {
return $this->_schema['tweets'];
}
public function connect() {
$this->connected = true;
$this->connection = new HttpSocketOauth();
return $this->connected;
}
public function close() {
if ($this->connected) {
unset($this->connection);
$this->connected = false;
}
}
}

Now that we have the basic datasource skeleton, we need to add the ability for our datasorce to connect to Twitter, using OAuth. Add the following methods to the TwitterSource:

class created before:public function token($callback = null) {
$response = $this->connection->request(array(
'method' => 'GET',
'uri' => array(
'host' => 'api.twitter.com',
'path' => '/oauth/request_token'
),
'auth' => array(
'method' => 'OAuth',
'oauth_callback' => $callback,
'oauth_consumer_key' => $this->config['key'],
'oauth_consumer_secret' => $this->config['secret']
)
));
if (!empty($response)) {
parse_str($response, $response);
if (empty($response['oauth_token']) && count($response) == 1 && current($response) == '') {
trigger_error(key($response), E_USER_WARNING);
} elseif (!empty($response['oauth_token'])) {
return $response['oauth_token'];
}
}
return false;
}
public function authorize($token, $verifier) {
$return = false;
$response = $this->connection->request(array(
'method' => 'GET',
'uri' => array(
'host' => 'api.twitter.com',
'path' => '/oauth/access_token'
),
'auth' => array(
'method' => 'OAuth',
'oauth_consumer_key' => $this->config['key'],
'oauth_consumer_secret' => $this->config['secret'],
'oauth_token' => $token,
'oauth_verifier' => $verifier
)
));
if (!empty($response)) {
parse_str($response, $response);
if (count($response) == 1 && current($response) == '') {
trigger_error(key($response), E_USER_WARNING);
} else {
$return = $response;
}
}
return $return;
}

Our datasource is now able to connect by requesting the proper authorization from Twitter. The next step is adding support to fetch tweets by implementing the datasource read() method. Add the following method to the TwitterSource:

class:public function read($model, $queryData = array()) {
if (
empty($queryData['conditions']['username']) ||
empty($this->config['authorize'])
) {
return false;
}
$response = $this->connection->request(array(
'method' => 'GET',
'uri' => array(
'host' => 'api.twitter.com',
'path' => '1/statuses/user_timeline/' . $queryData['conditions']['username'] . '.json'
),
'auth' => array_merge(array(
'method' => 'OAuth',
'oauth_consumer_key' => $this->config['key'],
'oauth_consumer_secret' => $this->config['secret']
), $this->config['authorize'])
));
if (empty($response)) {
return false;
}
$response = json_decode($response, true);
if (!empty($response['error'])) {
trigger_error($response['error'], E_USER_ERROR);
}
$results = array();
foreach ($response as $record) {
$record = array('Tweet' => $record);
$record['User'] = $record['Tweet']['user'];
unset($record['Tweet']['user']);
$results[] = $record;
}
return $results;
}

The job would not be complete if we are unable to post new tweets with our datasource. To finish our implementation, add the following method to the TwitterSource:

class:public function create($model, $fields = array(), $values = array()) {
if (empty($this->config['authorize'])) {
return false;
}
$response = $this->connection->request(array(
'method' => 'POST',
'uri' => array(
'host' => 'api.twitter.com',
'path' => '1/statuses/update.json'
),
'auth' => array(
'method' => 'OAuth',
'oauth_token' => $this->config['authorize']['oauth_token'],
'oauth_token_secret' => $this->config['authorize']['oauth_token_secret'],
'oauth_consumer_key' => $this->config['key'],
'oauth_consumer_secret' => $this->config['secret']
),
'body' => array_combine($fields, $values)
));
if (empty($response)) {
return false;
}
$response = json_decode($response, true);
if (!empty($response['error'])) {
trigger_error($response['error'], E_USER_ERROR);
}
if (!empty($response['id'])) {
$model->setInsertId($response['id']);
return true;
}
return false;
}

For the datasource to work, we will have to get OAuth authorization on all our requests to Twitter. To do so, we implement a method that will talk with the datasource to get the authorization keys, and handle the authorization callbacks Twitter will issue. Edit your app/controllers/tweets_controller.php and add the following contents at the beginning of the TweetsController class:

public function beforeFilter() {
parent::beforeFilter();
if (!$this->_authorize()) {
$this->redirect(null, 403);
}
}
protected function _authorize() {
$authorize = $this->Session->read('authorize'),
if (empty($authorize)) {
$source = $this->Tweet->getDataSource();
$url = Router::url(null, true);
if (
!empty($this->params['url']['oauth_token']) &&
!empty($this->params['url']['oauth_verifier'])
) {
$authorize = $source->authorize(
$this->params['url']['oauth_token'],
$this->params['url']['oauth_verifier']
);
$this->Session->write('authorize', $authorize);
} elseif (!empty($this->params['url']['denied'])) {
return false;
} else {
$token = $source->token($url);
$this->redirect('http://api.twitter.com/oauth/authorize?oauth_token=' . $token);
}
}
if (!empty($authorize)) {
$this->Tweet->getDataSource()->setConfig(compact('authorize'));
}
return $authorize;
}

Assuming your twitter account name is cookbook5, we now browse to http://localhost/tweets/index/cookbook5, and should see a paginated list of our tweets as shown in the following figure:

How to do it...

Using the form to post new tweets should submit our text to Twitter, and show us our new tweet in the listing.

How it works...

The Twitter datasource starts by specifying two new connection settings:

  • key: A Twitter application consumer key
  • secret: A Twitter application consumer secret key

It then defines a static schema, through the _schema property and the listSources() and describe() method implementations, to describe how a tweet post is built. This is done purely to add support for a Twitter based model to work with CakePHP's FormHelper. Doing so allows the FormHelper to determine what type of field to use when rendering a form for a Twitter-based model.

The connect() and close() methods simply instantiate and erase respectively an instance of the HttpSocketOauth class, which is our handler to communicate with the Twitter API.

Note

OAuth is a complicated process, and understanding it may prove to be a challenge. If you wish to obtain more detailed information about this protocol, there is probably no better resource than the Beginner's Guide to OAuth, available at http://hueniverse.com/oauth.

The token() method uses the connection to request a token from Twitter, which is needed for our requests to be successful. When one is obtained, we take the user to a specific Twitter URL using this token (the redirection takes place in the controller's _authorize() method), which is then used by Twitter to request the user for authorization.

If the user allows the access to his/her Twitter account, the Twitter API will redirect the browser to the URL specified in the callback argument of the datasource token() method. This callback was set in _authorize() as the current URL.

After the user is brought back to our application, the _authorize() method will check for the existence of two parameters sent by Twitter: oauth_token and oauth_verifier. These parameters are passed as arguments to the datasource authorize() method, which talks back to the Twitter API for the final stage in the OAuth authorization procedure. This stage ends with Twitter giving back a valid token, and a token secret key. They are saved in the controller as a session variable, to avoid doing this on every request.

Once we have the authorization information, we set it as a connection setting by using the setConfig() method available in all datasources, and setting this information in a setting named authorize, because we won't be able to read from or post to our Twitter account without this authorization.

The datasource read() method is the implementation of all read procedures on our datasource. In our case, we only allow find operations that contain a condition on the field username. This condition tells us from which user account we want to obtain tweets. Using this account name and the authorization information, we make a request to the Twitter API to obtain the user timeline. Because the request was made using JSON, which can be identified from the request URL), we use PHP's json_decode() function to parse the response. We then browse through the resulting items (if no error was thrown) and change them into a more friendly format.

The datasource write() method is the implementation of save operations, that is, the creating of new tweets (modification of existing tweets is not supported in this implementation). Similarly to the read() method, we use the authorization information to make a POST request to the Twitter API, specifying as the tweet data whatever fields were sent to the method (combination of the fields and values arguments).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.164.24