Using Zend_Paginator with Twitter API and Zend_Cache

Zend Framework 1.10.0 is out and a comment was posted on my blog that lead me to creating this new post. I’m going to focus more on Zend_Paginator and Zend_Rest_Client to access Twitters API since I’ve already created a post on Zend_Cache. Normally, I would use Zend_Service_Twitter to access the twitter service but it still seems to require authentication to retrieve a users timeline where only protected users should require authentication.

Zend_Paginator

Zend_Paginator from the Zend Framework site:

Zend_Paginator is a flexible component for paginating collections of data and presenting that data to users.

Zend_Paginator automatically creates pagination for you by setting up a few parameters and passing it an array of data. What is pagination, if you have ever gone to Google and searched for anything, usually you’ll see something like the following at the bottom of the search results page:

See the numbers and the text links, this is called pagination. So much data exists for the particular search that it wouldn’t make sense to display it all in one page. It would cause large amounts of scrolling down to view, the load time of the page would be affected, so we rather show fewer results and give our users the option of viewing more by clicking on the pagination links.

To demonstrate how to use Zend_Paginator I created a sample Zend Framework 1.10.0 application. This application grabs my last 50 tweets using the Twitter API and displays them 10 at a time using Zend_Paginator. I use Zend_Cache to cache my twitter data so I don’t have to spend time accessing their api every time – I’m sure they would appreciate it.

Bootstrap

The first step was to create a new zend framework project. I’m making the assumption that if you are reading this then you already know how to do this. After creating my new project, I added two methods to my bootstrap file to autoload and to init Zend_Cache. My bootstrap looks like the following:

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
	protected function _initAutoload()
	{
		$autoloader = new Zend_Application_Module_Autoloader(array(
            		'namespace' => 'My',
            		'basePath'  => APPLICATION_PATH,
        ));
	}

	protected function _initCache()
	{
		$front = array(
			'lifetime' => 10,
			'automatic_serialization' => true
		);

		$back = array(
			'cache_dir' => APPLICATION_PATH . '/../cache'
		);

		$cache = Zend_Cache::factory('Core', 'File', $front, $back);

		Zend_Registry::set('cache', $cache);
	}
}

For caching to work correctly, I had to create the ‘cache’ folder in my site root. Notice I set a cache lifetime in my bootstrap, I’ll be overriding it later for my twitter service. I like to set a general lifetime when caching and override the items that I believe need a different lifetime value.

Twitter Service

Next was creating my twitter service. I added a folder ‘service’ under ‘application’ and created a file called ‘Twitter.php’. Here is the content for that file:

class My_Service_Twitter
{
	const CACHELIFETIME = 3600;

	protected $_screen_name = null;
	protected $_cache = null;

	public function __construct($screen_name)
	{
		$this->_screen_name = $screen_name;

		$this->_cache = Zend_Registry::get('cache');
	}

	public function getUserTimeline($count)
	{
		$cache_id = 'twitter_' . $this->_screen_name . $count;

		if(!($data = $this->_cache->load($cache_id)))
    		{
    			$client = new Zend_Rest_Client('http://twitter.com/statuses/user_timeline.xml');

    			// setup params for service call
			$client->screen_name($this->_screen_name);
			$client->count($count);

			// call service and get all data
			$raw_data = $client->get();

			// create array with the data we want - text is the actual tweet 140 chars
			$data = array();
			if(!isset($raw_data->error))
			{
				foreach($raw_data->status as $tweet)
				{
					$data[]['text'] = (string)$tweet->text;
				}
			}

			// save array to cache - override cache lifetime
			$this->_cache->save($data, $cache_id, array('twitter'), self::CACHELIFETIME);
    		}

    		return $data;
	}
}

Let me explain what I’m doing here. First I setup some variables. I setup the cache lifetime var to an hour since I don’t want to be hitting twitter to often and I don’t tweet more than a couple times a day anyways. In the construct, I simply set my class properties. Twitter API has various service calls you can make. For the purpose of this app, I simply want to return the last 50 tweets posted by a public user – me. If you look at the twitter docs, you’ll see statuses user_timeline will give us just that. The call to the service is very simple. We create a Zend_Rest_Client instance to the service url: http://twitter.com/statuses/user_timeline.xml. Then we setup both parameters (check the link to the twitter api to see the rest of the available params) screen name (the twitter account you are looking for) and count (how many tweets to get back) and call $client->get(). When I get the raw data from twitter, I create an array storing only the data I need which is text (the actual content of your tweets 140 characters).

Easy enough? I thought so. You’ll also notice that this functionality is wrapped with caching code. Before calling the service, I check to see if this information is already cached and valid. If so, I just return the cached array else I call the service and store the array in cache. More info my Zend_Cache post on how Zend_Cache works.

IndexController

Next I added the following call to my IndexController:

public function indexAction()
{
    // needed vars
    $screen_name = 'joeyrivera';
    $total_tweets = 50;
    $tweets_per_page =  10;

    // get tweets
    $service = new My_Service_Twitter($screen_name);
    $data = $service->getUserTimeline($total_tweets);

    // init paginator
    $paginator = Zend_Paginator::factory($data);
    $paginator->setItemCountPerPage($tweets_per_page)
    			->setCurrentPageNumber($this->_getParam('page', 1));

    // send paginator to view
    $this->view->paginator = $paginator;
}

I started by setting up required params. Then initializing My_Service_Twitter and calling the getUserTimeline method.$data gets all 50 tweets from Twitter through Zend_Rest_Client or loaded straight from the cache. Last few steps are calling Zend_Paginator::factory and passing in the $data array of tweets. The next line sets the number of items we want displayed per page and sets the current page number – the page querystring variable (more options here). This last part will make sense soon when I show you the pagination.phtml (the actual pagination controls) file that renders the page numbers and previous/next links in the view. Since we are done with our paginator var, we pass it to our view to display.

Index View

The view is pretty simple, all it does is display the list of tweets, 10 tweets per page. It also needs to display the pagination controls which is the file I mentioned earlier. Here is the index.phtml file:

< ?php if (count($this->paginator)): ?>
    < ?php foreach ($this->paginator as $item): ?>
  • < ?php echo $item['text']; ?>
  • < ?php endforeach; ?>
< ?php endif; ?> < ?php echo $this->paginationControl($this->paginator, 'Sliding', 'pagination.phtml'); ?>

If any items are found, the loop takes place and the items are displayed in the list. Zend_Paginator takes care of handling how many items to show and for what page using that parameters we setup earlier. The last line is important. There are different styles you can use when displaying your pagination such as ‘sliding’, ‘jumping’, ‘elastic’ and more. You can read about them all in the zend docs. ‘pagination.phtml’ is the file used to display the page numbers and next/previous links. This file can be made so it’s reusable across your application and you can customize it anyway you like.

Pagination.phtml

Here is my file which is located in my views/scripts/ folder:

if($this->pageCount == 1)
{
	return;
}

// previous
if(isset($this->previous))
{
	echo '< Previous ';
}
else
{
	echo '< Previous '; } // pages foreach($this->pagesInRange as $page)
{
	if($page != $this->current)
	{
		echo ' '.$page.' ';
	}
	else
	{
		echo ' ', $page, ' ';
	}
}

// next
if(isset($this->next))
{
	echo ' Next >';
}
else
{
	echo ' Next >';
}

If there is only 1 page, do nothing else display the page numbers and if there is a previous or next page, display those links as well. Notice I’m setting a page variable to add to my querystring. This is so Zend_Paginator knows what page I’m currently at and can display the correct items. For example: ‘www.myapp.com/index/index/’ would default to page one. ‘www.myapp.com/index/index/page/3’ would let paginator know we want to display the items that belong to page 3. It also lets the pagination.phtml file know if there is a page 2 and/or 4 to display a back and previous link.

Here is a image of the end result:

Final Thoughts

That’s pretty much it. Hopefully I’m not forgetting anything. Notice I don’t have any filtering/validating anywhere. I just wrote this quickly to give a basic understanding on how to use these modules together. I think I’m going to update my ‘Interesting Images’ and ‘Interesting Links’ widgets on my sidebar to use pagination and allow users to view older entries.

Something to keep in mind for optimization purposes. Depending on where you are getting your data and how much data you want, it makes more sense to get a total number of items to get an idea of how many pages you need (not get all the data at once!). Then for each page you can get the data that needs to be displayed. For example, I would never call twitter and say: bring back my last one thousand tweets just so I can display 10 on a page and one hundred page numbers. That would be an extreme waste of resources. Instead, I could ask Twitter: how many tweets do I have in total which is all I need to draw my pagination. Then for each page, I can download the data I need and display.

Feel free to leave any questions or comments!

EDIT: Created new post on using Twitter API and OAuth.

15 thoughts on “Using Zend_Paginator with Twitter API and Zend_Cache”

  1. Pingback: abcphp.com
  2. Thanks for the complete example; I think I’d probably have used the json twitter api, but I doubt it makes any difference.

    Presumably Zend_Paginator can be used without having to fetch all possible results first? (guess I should read the docs)

    I had a look at Zend_Service_Twitter not so long ago; for my purposes (getting a trends listing) it was easier to make a direct json call, rather than Zend_Service_Twitter_Search (which doesn’t/didn’t allow for a custom httpclient object to be injected so it could cope with http_proxys. Therefore not particularly impressed with the design of Zend_Service_Twitter

  3. Hey David,

    When I update my blog sidebar for links and images I’ll most likely use Json, just so the user doesn’t have to leave the page to view more items. I haven’t giving it much thought (nor looked at the class), but I wonder if I’ll be able to use Paginator without hacking it to not rely on all results. Maybe I can create an empty array with the correct range but values only for the page needed.

    Calling the Twitter directly is already so easy to do that I haven’t felt a need to use the Zend_Service_Twitter as well. That and not being able to load my tweets without authenticating first.

    1. If I think I understand what you are about to do, it’s something I keep meaning to as well but haven’t had the time. I assume you want to add jquery calls to your pagination page numbers to load new data without a page refresh? If so, that’s a really good idea but I haven’t done that yet. If it’s a quick question about it I can certainly try to answer it.

Leave a Reply

Your email address will not be published. Required fields are marked *