Understanding the Zend Framework, Part 4: When there is no feed, the Zend_HTTP_Client

Building the perfect reader

This "Understanding the Zend Framework" series chronicles the building of an online feed reader, Chomp, while explaining the major aspects of using the recently introduced open source PHP Zend Framework. Part 3 explains how to use the Zend Framework to construct your online feed reader, Chomp, by creating an interface to subscribe to and read feeds, and to save feed entries into the database. Now you will learn how to use the Zend Framework to incorporate websites that do not support RSS feeds into the online feed-reader interface.

Tyler Anderson (tyleranderson5@yahoo.com), Engineer, Stexar Corp.

Tyler Anderson graduated with a degree in computer science from Brigham Young University in 2004 and is currently in his last semester as a master's student in computer engineering. In the past, he worked as a database programmer for DPMG.com, and he is currently an engineer for Stexar Corp., based in Beaverton, Ore.



18 January 2011 (First published 25 July 2006)

About this series

Part 1 talks about the overall concepts of the Zend Framework, including a list of relevant classes and a general discussion of the MVC pattern. Part 2 expands on that to show how MVC can be implemented in a Zend Framework application. You also created the user registration and login process, adding user information to the database and pulling it back out again.

Parts 3 and 4 deal with the actual RSS and Atom feeds. In Part 3, you enabled users to subscribe to individual feeds and to display the items listed in those feeds. You also discussed some of the Zend Framework's form-handling capabilities, validating data, and sanitizing feed items. Part 4 explains how to create a proxy to pull data from a site that has no feed.

The rest of the series involves adding value to the Chomp application. Part 5 explains how to use the Zend_PDF module to enable the user to create a customized PDF of saved articles, images, and search results. In Part 6, you use the Zend_Mail module to alert users to new posts. In Part 7, you look at searching saved content and returning ranked results. In Part 8, you create your own mashup, adding information from Amazon, Flickr, Twitter and Yahoo! And in Part 9, you add Ajax interactions to the site using JavaScript object notation.


Introduction

Not all sites have feeds, but it's still useful to track everything in one place. This article shows how to use the Zend_HTTP_Client module to create a proxy to pull data into the feed-reader interface. In this article, you will learn:

  • How to use the Zend_HTTP_Client module to load website data.
  • How to save the full text of feed entries and that of web pages not supporting feeds.
  • How to read the full text of saved feed entries within the feed-reader interface.

At the end of this article, you will have completed the framework of the feed-reader application. First, modify the database schema, then update the code to support the new schema. Next, add the functionality to save feed entry and web pages to the database. Finally, use Zend_HTTP_Client to allow users to optionally save the entries to the database and view them in your updated online feed reader.


Updating the database schema

To save feed entries into the feed-reader interface, you need to update the database schema first. Enter the SQL statements in Listing 1 in the MySQL console.

Listing 1. Modifying the database schema
drop table feeds;

create table feeds (feedname varchar(256), link varchar(512), rss varchar(5));
insert into feeds values
('Fox Sports', 'http://feeds.feedburner.com/foxsports/rss/headlines', 'true'), 
('Google News', 'http://news.google.com/?output=rss', 'true'), 
('Yahoo News', 'http://rss.news.yahoo.com/rss/topstories', 'true'),
('phpbb', 'http://www.phpbb.com/phpBB/viewforum.php?f=14', 'false'),
('MySQL Forums :: PHP', 'http://forums.mysql.com/list.php?52', 'false'),
('SitePoint Forums :: PHP', 'http://www.sitepoint.com/forums/forumdisplay.php?
    forumid=34', 'false');

drop table savedentries;

create table savedentries (id int(11) AUTO_INCREMENT, username varchar(20), 
feedname varchar(256), channelname varchar(256), link varchar(512), entrysaved 
varchar(5), entrydata text, PRIMARY KEY (id));

You can see that one field is added to the feeds table: rss. This tells you if the feed is an RSS feed or a web page that doesn't support feeds. Three more feeds of various PHP forums are added to the subscription list. Notice that the feeds from Part 3 have true in this new field, and the three new ones have false, indicating that they are web pages and not RSS feeds. The savedentries table has two new fields: entrysaved and entrydata. The entrysaved field indicates that the data in the entrydata field is valid. The entrydata field holds the full text of the article. You can see the new subscribable web pages in Figure 1.

Figure 1. Viewing the new subscribable web pages
Viewing the new subscribable web pages

Now it's time to go back to the Part 3 code and make some changes.


Updating the IndexController class

Later, you update the viewFeeds view, which requires the list of non-RSS feeds the current user is subscribed to, listing the subscribed feeds and web pages. Modify the indexAction method in the IndexController class, as shown in Listing 2.

Listing 2. The indexAction method in the IndexController class
    public function indexAction()
    {
...
            $select->where('feeds.feedname=subscribedfeeds.feedname');
            $select->where('feeds.rss=?', 'true');
            $rssResults = $db->fetchAll($select);

            $select = $db->select();
            $select->from('subscribedfeeds, feeds', '*');
            $select->where('subscribedfeeds.Username = ?', $username);
            $select->where('feeds.feedname=subscribedfeeds.feedname');
            $select->where('feeds.rss=?', 'false');
            $webResults = $db->fetchAll($select);

            $view = Zend::registry('view');
            $view->username = $username;
            $view->rssFeeds = $rssResults;
            $view->webFeeds = $webResults;
            echo $view->render('viewFeeds.php');
        }
    }

Two lists of results are obtained here: One contains the RSS feeds, and the other contains the web pages to which the current user is subscribed. They are then piped to the viewFeeds view and displayed.


Updating the saveEntryAction method

You need to update links that require you to save entries to the database, so you can update the two new fields. Modify the saveEntryAction method in the FeedController class, as shown in Listing 3.

Listing 3. The saveEntryAction method in the FeedController class
    public function saveEntryAction()
    {
        $input = new Zend_Filter_Input(
            array('username'=>'StringTrim'),
            array('username'=>'Alpha'),
            $_SESSION);
        $username = $input->getUnescaped('username');

        $input = new Zend_Filter_Input(
            array('*'=>'StringTrim'),
            null,
            $_POST);
        $feedTitle = $input->getUnescaped('feedTitle');
        $channelTitle = $input->getUnescaped('channelTitle');
        $channelLink = $input->getUnescaped('link');
        $type = $input->getUnescaped('type');
        $saveFullText = $input->getUnescaped 'saveFullText');
...
        $db = Zend_Registry::get('db');
        $row = array(
                     'Username' => $username,
                     'feedname' => $feedTitle,
                     'channelname' => $channelTitle,
                     'link' => $channelLink,
                     'entrysaved' => $saveFullText ? 'true' : 'false',
                     'entrydata' => $fullText
                     );
        
        $table = 'savedentries';
        $rowsAffected = $db->insert($table, $row);

        if($type == 'webPage')
            $this->_redirect("/");
        Else
            $this->_redirect("/feed/viewChannel?title=$feedTitle");
    }

The second section of Listing 3 takes the data from the POST array, instead of GET, since this is how requests to save entries are made (wouldn't want to send the full text of an article in the URL). Notice how the two new fields in the savedentries table are retrieved: type and saveFullText. The retrieved data is saved as a new row in the savedentries table, and if a web page is being saved, the user is sent back to the main page; otherwise, the user is sent back to the channel he was viewing.


Updating the deleteEntryAction method

You update the code where you deleted entries from the database. Modify the deleteEntryAction method in the FeedController class, as shown in Listing 4.

Listing 4. The deleteEntryAction method in the FeedController class
    public function deleteEntryAction()
    {
        $input = new Zend_Filter_Input(
            array('username'=>'StringTrim'),
            array('username'=>'Alpha'),
            $_SESSION);
        $username = $input->getUnescaped('username');

        $input = new Zend_Filter_Input(
            array('*'=>'StringTrim'),
            null,
            $_GET);
        $feedTitle = $input->getUnescaped('feedTitle');
        $channelTitle = $input->getUnescaped('channelTitle');
        $type = $input->getUnescaped('type');

        $db = Zend_Registry::get('db');
        $table = 'savedentries';
        $where = "username='$username' and feedname='$feedTitle'";
        if($type == 'rssFeed')
            $where = "$where and channelname='$channelTitle'";
        $rowsAffected = $db->delete($table, $where);
        
        $this->_redirect('/feed/viewSavedEntries/');
    }

The second section of code in Listing 4 retrieves the data from POST. Also, the type of entry is also retrieved. You see the new where clause, which also searches for a match on the channelname for RSS feeds, since web pages don't have a channelname.


Incorporating database changes and new functionality into the views

Now that the controllers are updated with new data being sent to the views, you need to update the views to capture this data and display it appropriately to the user.

The viewFeeds view

This view displays the subscribed feeds and websites to logged-in users. You need to modify this view to show the non-RSS websites currently subscribed to by customers. So, modify the viewFeeds.php file, as shown in Listing 5.

Listing 5. The viewFeeds view
...
         echo "<a href='feed/viewChannel?title=$feedTitle'>".
              "$feedTitle</a><br>";
     }
     ?>
  <br><br>
  <table>
    <tr>
      <td>Subscribed Web Pages:      
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
      <td>Save Entry to Database
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
      <td>Save Full Text</td>
    </tr>
  <?php
     foreach($this->webFeeds as $row){
         $feedTitle = $row['feedname'];
         $link = $row['link'];
         echo "<form method='POST' action='feed/saveEntry'>";
         echo "<input type='hidden' name='feedTitle' ".
              "value='$feedTitle'/>";
         echo "<input type='hidden' name='link' value='$link'/>";
         echo "<input type='hidden' name='type' value='webPage'/>";
         echo "<tr><td><a
 href='$link'>$feedTitle</a><br></td>";
         echo "<td><input type='submit'
 value='save'/></td>";
         echo "<td><input name='saveFullText' ".
              "type='checkbox'/></td></tr></form>";
     }
     ?>
  </table>
</body>
...

In Listing 5, a table is displayed that shows the subscribed web pages. As you loop through each entry, you embed hidden inputs that contain the page title, link, and type (webPage, as opposed to rssFeed), and you display a link to the actual web page, including a form to save the entry with a checkbox that allows users to save the full text of the page. See Figure 2.

Figure 2. The modified viewFeeds view
The modified viewFeeds

Try saving the "MySQL Forums :: PHP" page entry, along with its full text. You'll see what it looks like later.

The viewChannel view

You update the viewChannel view to include the form to save entries as in Listing 5. Modify the viewChannel view, as shown in Listing 6.

Listing 6. The modified viewChannel view
...
     <td>Save entry to database
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
      <td>Save Full Text</td>
    </tr>
  <?php
     $feedTitle = $this->title;
     foreach ($this->rssFeed as $item) {
         $entryTitle = $item->title();
         $link = $item->link();
         echo "<form method='POST' action='/feed/saveEntry'>";
         echo "<input type='hidden' name='feedTitle' ".
              "value='$feedTitle'/>";
         echo "<input type='hidden' name='title' ".
              "value='$entryTitle'/>";
         echo "<input type='hidden' name='link' value='$link'/>";
         echo "<input type='hidden' name='type' value='rssFeed'/>";
         echo "<tr><td><a href='$link'>$entryTitle</a><br></td>";
         echo "<td><input type='submit' value='save'/></td>";
         echo "<td><input name='saveFullText' ".
              "type='checkbox'/></td></tr></form>";s
     }
?>
  </table>
...

Notice the similarities above with that of Listing 5, but notice that the hidden value type is set to rssFeed, instead of webPage. View the updated example browser output in Figure 3.

Figure 3. The modified viewChannel view
The modified viewChannel view

The viewSavedEntries view

With entries being saved with and without full text into the database, you have to modify the viewSavedEntries view accordingly. Do so, as shown in Listing 7.

Listing 7. The viewSavedEntries view
...
      <td>Delete Channel Entry
       
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</td>
      <td>View Saved Full Text</td>
    </tr>
  <?php
     foreach ($this->entries as $row) {
         $link = $row['link'];
         $channelTitle = $row['channelname'];
         $feedTitle = $row['feedname'];
         $entrysaved = '';
         if($row['entrysaved'] == 'true')
             $entrysaved = 'Full Text'; 
         $title = "$feedTitle";
         if($row['channelname'] != ''){
             $title = "$title > $channelTitle";
             $type = 'rssFeed';
         } else {
             $type = 'webPage';
         }
         echo "<form method='POST' action='/feed/deleteEntry'>";
         echo "<input type='hidden' name='feedTitle' ".
              "value='$feedTitle'/>";
         echo "<input type='hidden' name='channelTitle' ".
              "value='$channelTitle'/>";
         echo "<input type='hidden' name='link' value='$link'/>";
         echo "<input type='hidden' name='type' value='$type'/>";
         echo "<tr><td><a
 href='$link'>$title</a></td>";
         echo "<td><input type='submit'
 value='delete'/></td>";
         echo "<td><a
 href='/feed/fullText?feedTitle=$feedTitle&".
              "channelTitle=$channelTitle'>".
              "$entrysaved</td></tr></form>";
     }
?>
  </table>
...

Modify the delete link into a form with a Delete button. In the foreach loop, check to see if the entry had its full text saved, and, if so, set the entrysaved variable to Full Text. Otherwise, it will remain empty. Then set up the title of the entry and its type. In the form, you embed four hidden inputs: the entry's feedname, channelname (empty for webPage types), full link, and type. Then you display the link to the actual web page, a button to delete the entry, and a link to the full text of the saved web page, if the full text was saved. Note that the action pointed to is /feed/fullText, so you define a new fullTextAction method in the FeedController in the next section. The updated viewSavedEntries view can be seen in Figure 4.

Figure 4. The modified viewSavedEntries view
The modified viewSavedEntries view

Saving feed entries

With the code in place for users to choose to save the full text of feed entries and web pages, the only thing missing is the code that grabs the web page using the Zend_HTTP_Client class. In this section, you define that code and the fullText action that displays the full text of saved entries to users.

Using Zend_HTTP_Client: saveEntryAction

It's time to complete the saveEntryAction method in the FeedController class by saving the full text of entries where the checkbox was checked. Modify the saveEntryAction method, as shown in Listing 8.

Listing 8. The saveEntryAction method in the FeedController class
    public function saveEntryAction()
    {
       $input = new Zend_Filter_Input(
            array('username'=>'StringTrim'),
            array('username'=>'Alpha'),
            $_SESSION);
        $username = $input->getUnescaped('username');
...
        if($saveFullText){
            $http = new Zend_Http_Client($channelLink);
            $response = $http->get();
            if ($response->isSuccessful())
                $fullText = $response->getBody();
            else{
                echo 'Error occurred, full text not saved, '.
                     'please reload.';
                return;
            }
        }

        $db = Zend::registry('db');
...
    }

If the checkbox indicates to save the entry, then grab it using the Zend_Http_Client class, as shown above. Save the full text of the entry into the fullText variable, which saves it into the database entry later in this method. If the retrieval was unsuccessful, an error message is displayed to the user who can try again by reloading the page.

Viewing full text of saved entries

Define the fullTextAction method so users can view the full text of saved entries. Define the fullTextAction method in the FeedController class, as shown in Listing 9.

Listing 9. The fullTextAction method in the FeedController class
    public function fullTextAction()
    {
       $input = new Zend_Filter_Input(
            array('username'=>'StringTrim'),
            array('username'=>'Alpha'),
            $_SESSION);
        $username = $input->getUnescaped('username');

        $db = Zend_Registry::get('db');
        $select = $db->select();
        $select->from('savedentries', '*');
        $select->where("username=?", $username);
        $select->where("feedname=?", $feedTitle);
        if($channelTitle)
            $select->where("channelname=?", $channelTitle);
        $sql = $select->__toString();
        $fullText = $db->fetchAll($sql);

        echo $fullText[0]['entrydata'];
    }

Grab the username, along with the feedname and channelname from the Session and Get arrays. Then search the savedentries table for a match, grab its full text and display it to the user. See the full text of the "MySQL Forums :: PHP" page entry saved in Figure 5.

Figure 5. Viewing the full text of a saved entry
Viewing the full text of a saved entry

Summary

That completes the feed reader! Use Zend_HTTP_Client to grab web pages from the Internet and save them into your feed reader. Your online feed reader also supports RSS feeds and web pages.

The rest of this series involves adding value to the Chomp application. Part 5 explains how to use the Zend_PDF module to enable the user to create a customized PDF of saved articles, images, and search results. In Part 6, you use the Zend_Mail module to alert users to new posts. In Part 7, you look at searching saved content and returning ranked results. In Part 8, you create your own mashup, adding information from Amazon, Flickr, and Yahoo. And in Part 9, you add Ajax interactions to the site using JavaScript object notation.


Download

DescriptionNameSize
Source codeos-php-zend4.source.zip10KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=145591
ArticleTitle=Understanding the Zend Framework, Part 4: When there is no feed, the Zend_HTTP_Client
publish-date=01182011