Display your Favorite Tweets using PHP and jQuery (Updated)

Demo Download

If you have a twitter account, you oftentimes find yourself looking for a way to display your latest tweets on your website or blog. This is pretty much a solved problem. There are jQuery plugins, PHP classes and tutorials that show you how to do this.

However, what happens if you only want to display certain tweets, that you have explicitly marked to show? As minimalistic twitter's feature set is, it does provide a solution to this problem - favorites.

In this tutorial, we will be writing a PHP class that will fetch, cache, and display your favorite tweets in a beautiful CSS3 interface. It will use Twitter's v1.1 OAuth API and the Codebird library.

Update (18 Jun, 2013): This tutorial originally used the discontinued Twitter v1 API, but was updated with the new, OAuth based one.

HTML

You can see the markup of the page that we will be using as a foundation below. The #container div will hold the tweets (which we will be generating in the PHP section of the tutorial).

index.php

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Display your Favorite Tweets using PHP and jQuery | Tutorialzine Demo</title>

        <!-- Our CSS stylesheet file -->
        <link rel="stylesheet" href="assets/css/styles.css" />

        <!--[if lt IE 9]>
          <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>

    <body>

        <div id="container">
            <!-- The tweets will go here -->
        </div>

        <!-- JavaScript includes -->
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
        <script src="assets/js/jquery.splitlines.js"></script>
        <script src="assets/js/script.js"></script>

    </body>
</html>

We will be using the splitLines plugin, which as its name suggest, will split the tweets into separate divs, one for each line of text. This is necessary as it is the only we can apply padding to the lines individually (as an illustration, view the demo with JS disabled). However, the demo will still keep most of its design without it.

As for the generation of the tweets, we will be creating a PHP class that will handle it for us. We will only need to call its generate method inside the #container div like this: $tweets->generate(5), which will show the 5 most recent liked tweets. This method will output an unordered list with tweets:

Tweet markup

<ul class="tweetFavList">
<li>
    <p>The text of the tweet goes here</p>
    <div class="info">
        <a title="Go to Tutorialzine's twitter page" class="user"
            href="http://twitter.com/Tutorialzine">Tutorialzine</a>

        <span title="Retweet Count" class="retweet">19</span>

        <a title="Shared 3 days ago" target="_blank" class="date"
            href="http://twitter.com/Tutorialzine/status/98439169621241856">3 days ago</a>
    </div>

    <div class="divider"></div>

</li>

<!-- More tweets here .. -->

</ul>

The text of the tweet will be held in a paragraph, with additional information available in the .info div. Now lets write the PHP class.

display-favorite-tweets-php-jquery-css.jpg
Display Your Favorite Tweets with jQuery and PHP

Create a Twitter Application

All requests to the twitter API have to be signed with API keys. The only way to obtain them is to create an application from Twitter’s developers’ site. Follow these steps:

  1. Go to https://dev.twitter.com and login with your twitter username and password;
  2. Click the “Create new application” button on the top-right;
  3. Fill in the required fields and click “Create”. After the app is created, it will have a read-only access, which is perfectly fine in our case;
  4. On the application page, click the “Create my access token”. This will allow the app to read data from your account as if it was you (read only). This is required by some of the API endpoints.

This will give you access tokens, client secrets and other keys, that you have to enter in index.php in the next step of the tutorial, for the demo to work.

PHP

We will name our class FavoriteTweetsList. It will take a set of twitter credentials as a parameter, and will create an internal instance of the Codebird PHP library for communicating with the Twitter API. It will automatically access the logged in user's favorite tweets, which if you followed the steps in the previous section, is you.

Our class will additionally support caching the response to a file, so that a request is made only once every three hours, which will speed things up.

FavoriteTweetsList.class.php

class FavoriteTweetsList{
    private $cb;
    const cache = "cache_tweets.ser";

    public function __construct($credentials){

        // Create an instance of the codebird class

        \Codebird\Codebird::setConsumerKey($credentials['twitter_consumer_key'], $credentials['twitter_consumer_secret']);

        $this->cb = \Codebird\Codebird::getInstance();

        // Your account settings
        $this->cb->setToken($credentials['twitter_access_token'], $credentials['twitter_token_secret']);

    }

    /* The get method returns an array of tweet objects */

    public function get(){

        $cache = self::cache;
        $tweets = array();

        if(file_exists($cache) && time() - filemtime($cache) < 3*60*60){

            // Use the cache if it exists and is less than three hours old
            $tweets = unserialize(file_get_contents($cache));
        }
        else{

            // Otherwise rebuild it
            $tweets = $this->fetch_feed();
            file_put_contents($cache,serialize($tweets));           
        }

        if(!$tweets){
            $tweets = array();
        }

        return $tweets;
    }

    /* The generate method takes an array of tweets and build the markup */

    public function generate($limit=10, $className = 'tweetFavList'){

        echo "<ul class='$className'>";

        // Limiting the number of shown tweets
        $tweets = array_slice($this->get(),0,$limit);

        foreach($tweets as $t){

            $id         = $t->id_str;
            $text       = self::formatTweet($t->text);
            $time       = self::relativeTime($t->created_at);
            $username   = $t->user->screen_name;
            $retweets   = $t->retweet_count;

            ?>

            <li>
                <p><?php echo $text ?></p>
                <div class="info">
                    <a href="http://twitter.com/<?php echo $username ?>" class="user"
                        title="Go to <?php echo $username?>'s twitter page"><?php echo $username ?></a>

                    <?php if($retweets > 0):?>
                        <span class="retweet" title="Retweet Count"><?php echo $retweets ?></span>
                    <?php endif;?>

                    <a href="http://twitter.com/<?php echo $username,'/status/',$id?>"
                        class="date" target="_blank" title="Shared <?php echo $time?>"><?php echo $time?></a>
                </div>

                <div class="divider"></div>

            </li>

            <?php
        }

        echo "</ul>";
    }

    /* Helper methods and static functions */

    private function fetch_feed(){

        // Create an instance of the Codebird class:
        return (array) $this->cb->favorites_list();
    }

    private static function relativeTime($time){

        $divisions  = array(1,60,60,24,7,4.34,12);
        $names      = array('second','minute','hour','day','week','month','year');
        $time       = time() - strtotime($time);

        $name = "";

        if($time < 10){
            return "just now";
        }

        for($i=0; $i<count($divisions); $i++){
            if($time < $divisions[$i]) break;

            $time = $time/$divisions[$i];
            $name = $names[$i];
        }

        $time = round($time);

        if($time != 1){
            $name.= 's';
        }

        return "$time $name ago";
    }

    private static function formatTweet($str){

        // Linkifying URLs, mentionds and topics. Notice that
        // each resultant anchor type has a unique class name.

        $str = preg_replace(
            '/((ftp|https?):\/\/([-\w\.]+)+(:\d+)?(\/([\w\/_\.]*(\?\S+)?)?)?)/i',
            '<a class="link" href="$1" target="_blank">$1</a>',
            $str
        );

        $str = preg_replace(
            '/(\s|^)@([\w\-]+)/',
            '$1<a class="mention" href="http://twitter.com/#!/$2" target="_blank">@$2</a>',
            $str
        );

        $str = preg_replace(
            '/(\s|^)#([\w\-]+)/',
            '$1<a class="hash" href="http://twitter.com/search?q=%23$2" target="_blank">#$2</a>',
            $str
        );

        return $str;
    }
}

Of the methods above, generate() is the one that you will most likely be working with directly. It takes the number of tweets to be displayed, and an optional class parameter, that overrides the default class attribute of the unordered list.

Now that we have the FavoriteTweetsList class in place, we simply need to instantiate an object, passing it a set of twitter credentials, like this:

index.php

require "includes/FavoriteTweetsList.class.php";
require "includes/codebird/codebird.php";

$tweets = new FavoriteTweetsList(array(
    'twitter_consumer_key'      => '-- Your consumer key here --',
    'twitter_consumer_secret'   => '-- Your consumer secret here -- ',
    'twitter_access_token'      => '-- Your access token here --',
    'twitter_token_secret'      => '-- Your access token secret --'
));

jQuery

As we are using the splitLines jQuery plugin, we already have most of the work done for us. We simply have to loop through the paragraph elements holding the text of the tweets, and call the plugin.

script.js

$(function(){
    var width = $('ul.tweetFavList p').outerWidth();

    // Looping through the p elements
    // and calling the splitLines plugin

    $('ul.tweetFavList p').each(function(){
        $(this).addClass('sliced').splitLines({width:width});
    });
});

This will split the contents of the paragraph into lines, each held in an individual div, which we can style.

CSS

First lets style the unordered list and the paragraph elements.

styles.css - 1

ul.tweetFavList{
    margin:0 auto;
    width:600px;
    list-style:none;
}

ul.tweetFavList p{
    background-color: #363636;
    color: #FFFFFF;
    display: inline;
    font-size: 28px;
    line-height: 2.25;
    padding: 10px;
}

/* Coloring the links differently */

ul.tweetFavList a.link      { color:#aed080;}
ul.tweetFavList a.mention   { color:#6fc6d9;}
ul.tweetFavList a.hash      { color:#dd90e9;}

If you take a closer look at the formatTweet() static method in the PHP class, you will see that we are adding a class name for each type of hyperlink - a regular link, a mention or a hash, so we can style them differently.

When the page loads, jQuery adds sliced as a class to each paragraph. This class undoes some of the styling applied to the paragraphs by default as a fallback, so we can display the individual lines properly.

styles.css - 2

/* The sliced class is assigned by jQuery */

ul.tweetFavList p.sliced{
    background:none;
    display:block;
    padding:0;
    line-height:2;
}

/* Each div is a line generated by the splitLines plugin */

ul.tweetFavList li p div{
    background-color: #363636;
    box-shadow: 2px 2px 2px rgba(33, 33, 33, 0.5);
    display: inline-block;
    margin-bottom: 6px;
    padding: 0 10px;
    white-space: nowrap;
}

Next we will style the colorful information boxes that hold the author username, publish date and retweet count.

styles.css - 3

ul.tweetFavList .info{
    overflow: hidden;
    padding: 15px 0 5px;
}

/* The colorful info boxes */

ul.tweetFavList .user,
ul.tweetFavList .retweet,
ul.tweetFavList .date{
    float:left;
    padding:4px 8px;
    color:#fff !important;
    text-decoration:none;
    font-size:11px;
    box-shadow: 1px 1px 1px rgba(33, 33, 33, 0.3);
}

ul.tweetFavList .user{
    background-color:#6fc6d9;
}

ul.tweetFavList .retweet{
    background-color:#dd90e9;
    cursor:default;
}

ul.tweetFavList .date{
    background-color:#aed080;
}

And finally we will style the divider. This is a single div, but thanks to :before/:after pseudo elements, we add two more circles to the left and to the right of it.

styles.css - 4

/* Styling the dotted divider */

ul.tweetFavList .divider,
ul.tweetFavList .divider:before,
ul.tweetFavList .divider:after{
    background-color: #777777;
    border-radius: 50% 50% 50% 50%;
    height: 12px;
    margin: 60px auto 80px;
    width: 12px;
    position:relative;
    box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.5);
}

ul.tweetFavList .divider:before,
ul.tweetFavList .divider:after{
    margin:0;
    position:absolute;
    content:'';
    top:0;
    left:-40px;
}

ul.tweetFavList .divider:after{
    left:auto;
    right:-40px;
}

ul.tweetFavList li:last-child .divider{
    display:none;
}

With this our favorited tweet list is complete!

Done

This example can be used to build a simple testimonials section, or to highlight tweets that you think your readers would find worthy. You can even see it implemented on the sidebar of this very site.

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles

cool! very nice. perfect for my site. big thanks.

I got this error at the error.log

[Fri Aug 05 16:18:24 2011] [error] [client 200.xxx.xxx.xxx] SoftException in Application.cpp:431: Mismatch between target GID (570) and GID (664) of file "index.php"

Martin Angelov

You are probably running into some kind of security error. Try chmod-ing the entire folder to 777 and if it does not help try Googling the error message. You could also ask your hosting provider.

Alice Wonder

Might I make suggestion?

Have your class use DOMDocument instead of print/echo raw html.

Advantage of using DOMDocument is your class could then be used in both web apps that use DOMDocument to construct the web page and in web apps that don't (by dumping the DOM and printing it).

Another huge advantage to using DOMDocument is that it makes the class inherently more secure as you won't end up with script injection resulting from browsers trying to adjust for malformed tags, a very common XSS vector method.

Since the class uses content scraped from elsewhere, it really should be done that way.

Alice Out

Martin Angelov

Sounds interesting. I will look into this.

WOwwww AwSome!!!

you are the man mArtin!!!!!!!!!!

thanq tanq tanq alooooooooooooooooooooooooot

this is awsome reallly awsome!

Thank you for this beautiful and useful tutorial. I really like the design in all of yours projects.

Amazing script with a great design!

i've got one question, how can i display tweets by hashtags?

Thanks (:

Martin Angelov

I think it should be possible to incorporate twitter' search API by replacing the URL in the fetch_feed() function.

looks perfect ;) thanks

e11world

very nice solution even though I never use the favorite on twitter.. very little but nice to know this tutorial integrates that.. thank you!

if i don't want to display my favorite tweets and i want to display my timeline what do i have to change?

thanks.

Martin Angelov

To fetch your regular tweets versus the favorited ones, replace the URL in the fetch_feed() function with this:

http://api.twitter.com/1/statuses/user_timeline.json?screen_name={$this->username}

Remember to remove the cache_twitter.ser file from your directory, otherwise you won't see the new results.

Just what I was looking! Very well explained. Thanks!

very nice twitter tutorial I like how you deal with twitter limitations you keep cash files. I have done almost the same in .net but store result in sql server. I think your aprrotch is better.

I had hoped that this script would entail something as simple as replacing a twitter username in the demo I've installed and voila, yet I cannot seem to locate where this username is assigned, other than $tweets = new FavoriteTweetsList('Tutorialzine');

What is a rookie like myself doing wrong?

Martin Angelov

This is the only change that is needed, but the tweets are probably cached. Deleting the cache file as mentioned in the article and reloading the page will fix the problem.

Eric Warnke

You actually need to include the PHP code:

$tweets->generate();

somewhere after you include the PHP file. This isn't styled as a code block in the tutorial and was easy to overlook, which I happened to also do the first time around.

video youtube

wow! amazing thanks!

Mathieu Rouault

Thanks for this tutorial. Unfortunately, the demo doesn't work (on the Firefox).

Great tut! I really like the idea.

Running into a slight issue. Fatal error: Call to a member function generate() on a non-object...
Thanks for the wonderful tutorial!

Its not working. it keeps giving errors. tried a lot.
little help will be appreciated.

Warning: require(includes/FavoriteTweetsList.class.php): failed to open stream: No such file or directory in - on line 3 Fatal error: require(): Failed opening required 'includes/FavoriteTweetsList.class.php' (include_path='.:') in - on line 3

TheLoneCuber

Awesome script Martin. Thanks for publishing it.

I'm using it inside a WordPress theme and won't to use it on more than one theme. But because the cache gets written to the root directory, only one username works.

Is it possible to change the directory to which the cache gets written? If I can write it to a theme folder then I can use the script for more than one website on the same server.

Martin Angelov

It would be easier to prepend the Twitter username to the cache file name.

Is there a way to do this and publish instagram photos posted with tweets?

Jack Barham

I have got it working on a static site but can't get it to work on WordPress themes. I have tried to change the heading to...

require "/home/kategill/public_html/new-dev/wp-content/themes/airpress/library/tweets/includes/FavoriteTweetsList.class.php";

But I keep getting an error message of...

Fatal error: Call to a member function generate() on a non-object in /home/kategill/public_html/new-dev/wp-content/themes/airpress/page-home.php on line 47

Does anyone know how to set this properly in WordPress?

Thanks - J

Hi Martin. Excellent tutorial!

I ran in a problem which I finally solved after a lot of time. I have my page hosted in iPage and I couldn't get the tutorial to work. I got the same fatal error as Jack Barham.
After a while I realized that the problem was that I was exceeding the rate limit of the Twitter API. That was because I was using it in the unauthorized mode, so what I did was create a token for my app and retrieved the favorite tweets using the Twitter Oauth library written by @abraham.
If you want my code please mail me and I will send it without any problem so you can publish it.

In a few words, the problem was that Twitter limits the amount of request you can make and I was getting the limit based on the entire iPage requests. The solution was to retrieve the tweets in an authenticated way so I get the rate limit based on my user requests and no iPage requests.

Eric Warnke

Awesome! We totally use this at Backup Box :)

chloédigital

Fantastic tutorial, thanks so much. Helped ALOT

Tom Allen

Using this for Wordpress, I modified the class to use WP's built-in caching methods:

public function get(){

    $tweets = array();

    $cache = get_transient('wp_homepage_tweets');

    if ($cache) {
        $tweets = $cache;
    }
    else {
        $tweets = json_decode($this->fetch_feed());
        set_transient('wp_homepage_tweets',$tweets,3*60*60);
    }

    if(!$tweets){
        $tweets = array();
    }

    return $tweets;
}