Get Twitter RSS Feeds with CakePHP

By Darren Moore (zeen)
Using CakePHP model get your RSS twitter feeds with caching and limits.

Installation


  1. Save this script below into your models directory
  2. Change your Twitter ID and Twitter Name
  3. Add to your $uses controller variable, public $uses = array('Twitter')
  4. Add to your controller method, $this->set('twits',$this->Twitter->find());
  5. Then add to your view


Model Class:

Download code <?php 
    
/**
     * Get Twitter Updates
     *
     * $twits = $this->Twitter->find(array('cache'=>true,'limit'=>8));
     *
     * @author        Darren Moore, zeeneo@gmail.com
     * @link          http://www.zeen.co.uk
     */
    
class Twitter extends AppModel
    
{
        
/**
         * Your Twitter ID
         *
         * @var integer
         * @access public
         */
        
public $twitterId 14210082;
        
        
/**
         * Remove your name from posts
         * Set to false to not remove your name, otherwise set to your name
         *
         * @var mixed
         * @access public
         */
        
public $twitterName 'zeeneo';
        
        
/**
         * Show replies to people
         *
         * @var boolean
         * @access public
         */
        
public $showReplies false;
        
        
/**
         * Twitter RSS URL
         *
         * @var string
         * @access public
         */
        
public $rssUrl 'http://twitter.com/statuses/user_timeline/:twitterId.rss';
        
        
/**
         * Turn off table usage
         *
         * @var string
         * @access public
         */
        
public $useTable false;
        
        
/**
         * Duration of cache
         *
         * @var string
         * @access public
         */
        
public $cacheDuration '+30 mins';
    
    
        
/**
         * Find Twitters
         *
         * @param array $options Options when getting twits, as followed:
         *                          - cache: Force caching on or off
         *                          - limit: Limit number of records returned
         * @access public
         * @return array
         */
        
public function find($options = array())
        {
            
//Get twits
            
if((isset($options['cache']) && $options['cache'] == false) || ($twits Cache::read('Twitter.lines')) == false)
            {
                
$twits $this->_getTwits();
                
Cache::set(array('duration' => $this->cacheDuration));
                
Cache::write('Twitter.lines',$twits);
            }
            
            
//Set to limit
            
if(isset($options['limit']) && count($twits) > $options['limit'])
            {
                
$twits array_slice($twits0$options['limit']);
            }
            
            return 
$twits;
        }
        
        
/**
         * Get Twitter Lines
         * 
         * @access private
         * @return array
         */
        
private function _getTwits()
        {        
            
//Get feed
            
$ch curl_init();
            
curl_setopt($chCURLOPT_URL,String::insert($this->rssUrl,array('twitterId'=>$this->twitterId)));
            
curl_setopt($chCURLOPT_CONNECTTIMEOUT2);
            
curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
            
$feed curl_exec($ch);
            
curl_close($ch);
            
            if(!
$feed) { return false; }
            
            
$xml = new SimpleXmlElement($feed);
            
            foreach(
$xml->channel->item as $item)
            {
                
//
                
$title = (string)$item->title;
            
                
//Skip if it's a reply
                
if(!$this->showReplies && preg_match('/^'.$this->twitterName.': @/',$title))
                    continue;
            
                
//Remove name
                
if($this->twitterName)
                    
$title trim(preg_replace('/^'.$this->twitterName.':/','',$title));
            
                
$out[] = array(
                    
'title' => $title,
                    
'description' => (string)$item->description,
                    
'pubDate' => strtotime($item->pubDate),
                    
'link' => (string)$item->link
                
);
            }
            
            return 
$out;
        }
    
    }

?>

 

Comments 913

CakePHP Team Comments Author Comments
 

Comment

1 Nice

This was exactly what I was looking for, thanks!
Posted Jan 17, 2009 by Ivan
 

Comment

2 Multiple accounts

I've modified this code to allow for multiple accounts to be integrated into one feed (currently using on a band's website). I'll post the code if anyone wants it..
Posted Jan 21, 2009 by Sean McEmerson
 

Comment

3 Trusted Data?

I do not understand well enough, if you are cleaning the data anywhere? No matter if twitter or any other source - if you do not check and sanitize the data you pull from other sites, you will open your site to potential attacks. Is the data cleaned automagically by cake in this case? If yes, please explain. If not, please change the code.
I think it would be quite important to keep an eye on the quality of tutorials that are posted here. People will adapt bad techniques published here and problems will fall back on the cakephp project as "not beeing safe" or something. Thanks!
Posted Mar 6, 2009 by Johnny Cake
 

Comment

4 Having some difficulty with trying this

Disclaimer - I am 1 day old to CakePhp and trying to pick it up for my next app.

That said.
1. I created the model as you've instructed called twitter.php under models folder
2. I changed the twitter id and the twitter username
3. I created a controller called TwittersController under controller folder
4. Added the code you mentioned, whch is here
class TwittersController extends Controller{
var $uses= array("Twitter"); // this is the model to use
//var $autoRender=false; // this specifies the view to use

function tweet(){
$this->set('twits',$this->Twitter->find());
}
}
?> 5. I am not using any view for now.

When I run this as
http://localhost/cake/twitters/tweet

it gives me an error stating:
Fatal error: Call to undefined function curl_init() in C:\xampp\htdocs\cake\app\models\twitter.php on line 99

Thanks for help in advance
Posted Mar 11, 2009 by Guns
 

Comment

5 Escaping

I do not understand well enough, if you are cleaning the data anywhere? No matter if twitter or any other source - if you do not check and sanitize the data you pull from other sites, you will open your site to potential attacks. Is the data cleaned automagically by cake in this case? If yes, please explain. If not, please change the code.
I think it would be quite important to keep an eye on the quality of tutorials that are posted here. People will adapt bad techniques published here and problems will fall back on the cakephp project as "not beeing safe" or something. Thanks!

I have not included a view example. It is up to you how you escape the output and you'll do this in your view file.
Posted Jul 8, 2009 by Darren Moore
 

Comment

6 curl

SNIP

it gives me an error stating:
Fatal error: Call to undefined function curl_init() in C:\xampp\htdocs\cake\app\models\twitter.php on line 99

Thanks for help in advance

You need to have curl installed on your server. I will update this script soon to use HttpSocket
Posted Jul 8, 2009 by Darren Moore
 

Comment

7 Updates

Hi people.

When I get a spare 10 minutes I'll update this code to use HttpSocket, optional table caching and a few other clean ups, like using the word 'tweet' instead of 'twit' :)
Posted Jul 8, 2009 by Darren Moore
 

Comment

8 improvements

i enhanced the code (+ fixed some stuff that would have thrown warnings etc..):
- reset + indirect access functionality
- several twitterAccounts possible
- error handling (+ retrieval)


<?php
    
class Twitter extends AppModel {
        
/**
         * Twitter ID
         *
         * @var integer
         */
        
private $twitterId null;
        
        
/**
         * Remove your name from posts
         * Set to false to not remove your name, otherwise set to your name
         *
         * @var mixed
         */
        
private $twitterName '';
        
        
/**
         * Show replies to people
         *
         * @var boolean
         */
        
private $showReplies false;
        
        
/**
         * Twitter RSS URL
         *
         * @var string
         */
        
private $rssUrl 'http://twitter.com/statuses/user_timeline/:twitterId.rss';
        
        
/**
         * Turn off table usage
         *
         * @var string
         * @access public
         */
        
public $useTable false;
        
        
/**
         * Duration of cache
         *
         * @var string
         */
        
private $cacheDuration '+30 mins';

        
/**
         * Duration of cache
         *
         * @var string
         */
        
private $logErrors true;
            
        
/**
         * Error
         *
         * @var string
         */
        
private $error '';
      
    
    
        public function 
__construct() {
            
parent::__construct();
            
# try to get default id from configs
            
$id Configure::read('Twitter.id');
            if (!empty(
$id)) {
                
$this->setId();
               }
        }
        
        public function 
reset($resetAccount false) {
            
$this->error '';
            if (
$resetAccount) {
                
$this->twitterId null;
            }
        }
        
        public function 
setAccount($id$name null) {
            
$this->reset();
            
$this->twitterId $id;
            if (!empty(
$name)) {
                
$this->twitterName $name;
            }
        }
        
        public function 
logErrors($log true) {
            
$this->logErrors $log;
        }
        
          public function 
setDuration($cacheDuration) {
               
$this->cacheDuration $cacheDuration;
        }      
            
    
        
/**
         * Find Twitters
         *
         * @param array $options Options when getting twits, as followed:
         *                          - cache: Force caching on or off
         *                          - limit: Limit number of records returned
         *                             - duration: Cache Duration (if caching is on)
         * @access public
         * @return array
         */
        
public function find($options = array()) {
            if (empty(
$this->twitterId)) {
                
$this->_setError('no twitterAccount found'true);
                return array();
            }
            
            
# setDuration
            
if(!empty($options['duration'])) {
                
$this->setDuration($options['duration']);
            }
            
            
# Get twits
            
if((isset($options['cache']) && $options['cache'] == false) || ($twits Cache::read('Twitter.'.$this->twitterId.'.lines')) == false)
            {
                
$twits $this->_getTwits();
                
Cache::set(array('duration' => $this->cacheDuration));
                
Cache::write('Twitter.'.$this->twitterId.'.lines',$twits);
            }
            
            
# Set to limit
            
if(isset($options['limit']) && count($twits) > $options['limit'])
            {
                
$twits array_slice($twits0$options['limit']);
            }
            
            return 
$twits;
        }
        
        
/**
         * Get Twitter Lines
         * 
         * @access private
         * @return array
         */
        
private function _getTwits() {
            
$out = array();
            
            
# Get feed
            
$ch curl_init();
            
curl_setopt($chCURLOPT_URL,String::insert($this->rssUrl,array('twitterId'=>$this->twitterId)));
            
curl_setopt($chCURLOPT_CONNECTTIMEOUT2);
            
curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
            
$feed curl_exec($ch);
            
curl_close($ch);
            
            if(!
$feed) { 
                
$this->_setError('invalid request'true);
                return array(); 
            }
            
            
$xml = new SimpleXmlElement($feed);
            
            if (!empty(
$xml->error)) {
                
$this->_setError((string)$xml->error); # usually no log entry neccessary
                
return array();
            }
            
            foreach(
$xml->channel->item as $item) {
                
//
                
$title = (string)$item->title;
            
                
//Skip if it's a reply
                
if(!$this->showReplies && preg_match('/^'.$this->twitterName.': @/',$title))
                    continue;
            
                
//Remove name
                
if($this->twitterName) {
                    
$title trim(preg_replace('/^'.$this->twitterName.':/','',$title));
                }
                
$out[] = array(
                    
'title' => $title,
                    
'description' => (string)$item->description,
                    
'pubDate' => strtotime($item->pubDate),
                    
'link' => (string)$item->link
                
);
            }
            
            return 
$out;
        }
    
        
/**
         * return error (if any)
         * @return string
         */
        
public function error() {
            return 
$this->error;
        }
        
        private function 
_setError($error$log false) {
            
$this->error $error;
            if (
$log && $this->logErrors) {
                
$this->log('ID: '.(string)$this->twitterId.' - '.$error,'twitter');
            }
        }
    
    }
?>
Posted Jul 12, 2009 by Mark
 

Comment

9 testing the twitter model

by the way:
Test-Cases are always a great way to ensure the model is working correctly:

<?php 
App
::import('Model''Twitter');

class 
TwitterTestCase extends CakeTestCase {
    var 
$Twitter null;
    
    function 
startTest() {
        
$this->Twitter =& ClassRegistry::init('Twitter');
    }

    function 
testTwitterInstance() {
        
$this->assertTrue(is_a($this->Twitter'Twitter'));
    }

    function 
testTwitterFind() {
        
# valid ID
        
$id 14210082
        
$this->Twitter->setAccount($id);
        
        
$results $this->Twitter->find();
        
$this->assertTrue(!empty($results));
        
        
pr ($results);
        
        
# invalid ID
        
$id 14210081
        
$this->Twitter->setAccount($id);
        
$results $this->Twitter->find();
        
$this->assertTrue(empty($results));
        
        
$error $this->Twitter->error();
        
$this->assertTrue(!empty($error));
        
pr ($error);
    }
}
?>

Posted Jul 12, 2009 by Mark
 

Comment

10 Addons: Part Two

For everybody who needs an even more powerful twitter model:
- supports channelInfos, twitterName (cached as well)
- now multiple find($accountId) possible (auto-reset feature)
- you can retrieve tweets by either twitterId or twitterName
- error logging for severe errors

<?php 
    
/**
     * Get Twitter Updates
     * $twits = $this->Twitter->find($id, array('cache'=>true,'limit'=>8));
     * NOTE: don't forget to sanitize or escape output: h() should work for most cases
     */
    
class Twitter extends AppModel {
        
/**
         * Twitter ID
         * @var integer/string
         */
        
private $twitterId null;
        
        
/**
         * Twitter Name
         * @var string
         */
        
private $twitterName '';

        
/**
         * Remove twitterName from posts
         * Set to false to not remove your name, otherwise set to your name 
         * @var boolean
         */        
        
private $removeTwitterName false;
        
        
/**
         * Channel info
         * @var array
         */
        
private $twitterChannel = array();
                
        
/**
         * Show replies to people
         * @var boolean
         */
        
private $showReplies false;
        
        
/**
         * Twitter RSS URL
         * @var string
         */
        
private $rssUrl 'http://twitter.com/statuses/user_timeline/:twitterId.rss';
        
        
/**
         * Turn off table usage
         * @var string
         * @access public
         */
        
public $useTable false;
        
        
/**
         * Duration of cache
         * @var string
         */
        
private $defaultDuration '+30 mins';
        private 
$cacheDuration '';

        
/**
         * Duration of cache
         * @var string
         */
        
private $logErrors true;
            
        
/**
         * Error
         * @var string
         */
        
private $error '';
      
    
    
        public function 
__construct() {
            
parent::__construct();
            
$this->reset(true);
            
            
# try to get default id from configs
            
$id Configure::read('Twitter.id');
            if (!empty(
$id)) {
                
$this->setAccount($id);
               }
               
               
# set the cacheDuration to default one (can be overridden by setDuration() later on)
               
$duration Configure::read('Twitter.cache_duration');
               if (!empty(
$duration)) {
                   
$this->cacheDuration $duration;
            } else {
                
$this->cacheDuration $this->defaultDuration;
            }
        }
        
        
/**
         * reset just error message or all values
         * @param boolean $resetWholeAccount (default: false)
         */
        
public function reset($resetAccount false) {
            
$this->error '';
            if (
$resetAccount) {
                
$this->twitterId null;
                
$this->twitterName '';
                
$this->twitterChannel = array(
                    
'title' => '',
                    
'link' => '',
                    
'description' => '',
                    
'language' => '',
                    
'ttl' => '',    
                );
            }
        }

        
        
/**
         * @param int/string $account: twitter id or twitter name
         * @param string $name: twitter name (optional)
         */
        
public function setAccount($account$name null) {
            if (empty(
$account)) {
                return 
false;
            }
            
            
$this->reset(true);
            
$this->twitterId $account;
            if (!empty(
$name)) {
                
$this->twitterName $name;
            } elseif (!
is_numeric($account) || strlen($account) != 8) {
                
$this->twitterName $account;
            }
            return 
true;
        }
        
        
/**
         * retrieve current twitter accountName
         */
        
public function name() {
            return 
$this->twitterName;
        }
 
         
/**
         * retrieve twitter channelInfos for current account
         */       
        
public function channel() {
            return 
$this->twitterChannel;
        }
        
        
/**
         * enable/disable logging of (severe) errors
         */
        
public function logErrors($value true) {
            
$this->logErrors = (bool)$value;
        }

        
/**
         * enable/disable removal of twitterName from 'twitterName: xyz' (defaults to FALSE)
         */
        
public function removeName($value true) {
            
$this->removeTwitterName = (bool)$value;
        }        
        
        
/**
         * setDuration with string '+x Minutes' etc or with int 'x' seconds
         * tip: null as param resets duration
         */
          
public function setDuration($cacheDuration null) {
              if (
$cacheDuration === null) {
                  
$cacheDuration $this->defaultDuration;
              }
               
$this->cacheDuration $cacheDuration;
               return 
true;
        }      
            
        
/**
         * Find Twitters
         * @param int/string $account: twitter id or twitter name (optional)
         *  - leave empty if defined before (or if set via Configure::write('Twitter.id'))
         * @param array $options: Options when getting twits, as followed:
         *  - cache: Force caching on or off
         *  - limit: Limit number of records returned
         *  - duration: Cache Duration (if caching is on)
         * @access public
         * @return array
         */
        
public function find($account null$options = array()) {
            
$this->setAccount($account);
            
            if (empty(
$this->twitterId)) {
                
$this->_setError('no twitterAccount found'true);
                return array();
            }
            
            
# setDuration
            
if(!empty($options['duration'])) {
                
$this->setDuration($options['duration']);
            }
            
            
# get twits
            
if((isset($options['cache']) && $options['cache'] == false) || (($twits $this->_getCachedTwits()) === false)) {
                
$twits $this->_getTwits();
                
$this->_setCachedTwits($twits);
            }
            
            
# set to limit
            
if(isset($options['limit']) && count($twits) > $options['limit'])
            {
                
$twits array_slice($twits0$options['limit']);
            }

            return 
$twits;
        }
        
        
/**
         * get cached twitterItems, twitterCannel, twitterName
         */
        
private function _getCachedTwits() {
            
Cache::set(array('duration' => $this->cacheDuration));
            
$twits Cache::read('Twitter.'.$this->twitterId.'.lines');
            
            if (
$twits != false && isset($twits['items'])) {
                
$this->twitterChannel $twits['channel'];
                
$this->twitterName $twits['name'];
                
$this->error $twits['error'];
                return 
$twits['items'];
            }         
            return 
false;
        }
        
        
/**
         * cache twitterItems, twitterCannel, twitterName
         * @todo can some specialchared twitterNames cause problems in filename???
         * @todo save in subfolder of "cache" (not in root level!)
         */
        
private function _setCachedTwits($twits) {
            
$twits = array(
                
'items' => $twits,
                
'channel' => $this->twitterChannel,
                
'name' => $this->twitterName,
                
'error' => $this->error,
            );
            
               
Cache::write('Twitter.'.$this->twitterId.'.lines',$twits);
               return 
true;
           }

        
/**
         * Get Twitter Lines
         * @access private
         * @return array
         */
        
private function _getTwits() {
            
$out = array();
            
            
# Get feed
            
$ch curl_init();
            
curl_setopt($chCURLOPT_URL,String::insert($this->rssUrl,array('twitterId'=>$this->twitterId)));
            
curl_setopt($chCURLOPT_CONNECTTIMEOUT2);
            
curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
            
$feed curl_exec($ch);
            
curl_close($ch);
            
            if(!
$feed) { 
                
$this->_setError('Invalid request'true);
                return array(); 
            }
            
            
$xml = new SimpleXmlElement($feed);

            if (!empty(
$xml->error)) {
                
$this->_setError((string)$xml->error); # usually no log entry neccessary
                
return array();
            }
            
            
$this->twitterChannel = array(
                
'title' => (string)$xml->channel->title,
                
'link' => (string)$xml->channel->link,
                
'description' => (string)$xml->channel->description,
                
'language' => (string)$xml->channel->language,
                
'ttl' => (int)$xml->channel->ttl,
            );
            
            
$twitterName trim(str_replace('http://twitter.com/','',$xml->channel->link));
            if (!empty(
$twitterName)) {
                
$this->twitterName $twitterName;
            }
            foreach(
$xml->channel->item as $item) {
                
$title = (string)$item->title;
            
                
//Skip if it's a reply
                
if(!$this->showReplies && preg_match('/^'.$this->twitterName.': @/',$title))
                    continue;
            
                
//Remove name
                
if($this->removeTwitterName && !empty($this->twitterName)) {
                    
$title trim(preg_replace('/^'.$this->twitterName.':/','',$title));
                }
                
$out[] = array(
                    
'title' => $title,
                    
'description' => (string)$item->description,
                    
'pubDate' => strtotime($item->pubDate),
                    
'link' => (string)$item->link
                
);
            }

            return 
$out;
        }

        
/**
         * return latest error (if any)
         * @return string
         */
        
public function error() {
            return 
$this->error;
        }
        
        private function 
_setError($error$log false) {
            
$this->error $error;
            if (
$log && $this->logErrors) {
                
$this->log('ID: '.(string)$this->twitterId.' - '.$error,'twitter');
            }
        }

    }
?>


Usage:
<?php
$this
->Twitter->setAccount($account);
$this->set('twits',$this->Twitter->find());

or

Configure::write('Twitter.id'$account); // in configs e.g.
$this->set('twits',$this->Twitter->find());

or 
just

$this
->set('twits',$this->Twitter->find($account$options));
?>
Posted Jul 13, 2009 by Mark
 

Bug

11 Doesn't work

This doesn't work. I get an empty array.

Controller Class:

<?php 
$lastTweet 
$this->Twitter->find('myTwitterUsername', array('cache'=>true,'limit'=>10));
debug($lastTweet);
?>
Posted Aug 27, 2009 by Rahil Sondhi
 

Comment

12 Online/offline conflict

Just like Rahil Sondhi I seem to be getting an empty array. The only problem is that this only happens when accessing online, not when my app runs locally. I first figured this had to do something with the cache, but honestly I'm stuck right now.

The frustrating thing to me is that from time to time it will work, yet when I visit half an hour later, it won't.

Does anybody have an explanation for me? I a using the optimized model by Mark.

Controller:

<?php 
class TweetsController extends AppController {

    public 
$uses = array('Twitter');
    var 
$name 'Tweets';

    function 
index(){
       
$export $this->Twitter->find('stefanvanaken',array('limit'=>6));
       return 
$export;
    }

?> 

Element:

<div id="twitter_box">
    <?=$html->link($html->image('layout/twitter.png',array('border'=>0,'alt'=>'@stefanvanaken','class'=>'event_logo')),'http://twitter.com/stefanvanaken',array('target'=>'_blank'),false,false);?>
    
    <? $tweets = $this->requestAction('tweets/index'); ?>
    
    <?     foreach ($tweets as $tweet) { ?>
        <p><span style="font-size:10px;margin-bottom:10px;"><?=date('d-m-Y \o\m G:i',$tweet['pubDate']);?></span><br>
            <?=$tweet['title'];?>
        </p>
    <?     } ?>
</div>

Thanks a million!
Posted Aug 28, 2009 by Stefan van Aken
 

Comment

13 Thanks

Thanks for the classes
Posted Nov 4, 2009 by Nicolas Chevallier