《Agile Web Application Development with Yii1.1 and PHP5》

Chapter 10: Iteration 7: Adding an RSS Web Feed

In the previous iteration, we added the ability for the user to leave comments on issues and to display a list of these comments utilizing a portlet architecture to allow us to easily and consistently display that listing anywhere throughout the application. In this iteration, we are going to build upon this feature and expose this list of comments as an RSS data feed. Furthermore, we are going to use the existing feed functionality available in another open source framework, the Zend Framework, to demonstrate just how easy it is for a Yii application to integrate with other third-party tools.

Iteration planning

The goal of this iteration is to create an RSS feed using the content created from our user generated comments. We should allow users to subscribe to a comment feed that spans all projects as well as subscribe to individual project feeds. Luckily, the widget functionality we built previously has the capability to return a list of recent comments across all projects, as well as restrict the data to one specific project. So, we have already coded the appropriate methods to access the needed data. The bulk of this iteration will focus on putting that data in the correct format to be published as an RSS feed, and adding links to our application to allow users to subscribe to these feeds.

The following is a list of high-level tasks we will need to complete in order to achieve these goals:

  • Download and install Zend Framework into the Yii application
  • Create a new action in a controller class to respond to the feed request and return the appropriate data in an RSS format
  • Alter our URL structure for ease of use
  • Add our newly created feed to both the projects listings page, as well as to each individual project details page

As always, be sure to run the full suite of unit tests prior to making any changes to ensure everything that is still working as expected.

A little background: Content Syndication, RSS, and Zend Framework

Web Content Syndication has been around for many years, but has recently gained enormous popularity. The term Web Content Syndication refers to publishing information in a standardized format so that it can easily be used by other websites and easily consumed by reader applications. Many news sites have long been electronically syndicating their content, but the massive explosion of web logs (also known as blogs) across the Internet has turned Content Syndication (also known as known as feeds) into an expected feature of almost every website. Our TrackStar application will be no exception.

RSS is an acronym that stands for Really Simple Syndication. It is an XML format specification that provides a standard for Web Content Syndication. There are other formats that could be used, but due to the overwhelming popularity of RSS among most websites, we will focus on delivering our feed in this format.

Zend is known as "The PHP Company". Their founders are key contributors to the core PHP language and the company focuses on creating products to help improve the entire PHP application development life-cycle experience. They provide products and services to help with configuration and installation, development, deployment and with production application administration and maintenance. One of the products they offer to assist in application development is the Zend Framework. The framework can be used as a whole to provide an entire application foundation, much in the same way we are using Yii for our TrackStar application, or piece-meal by utilizing single feature components of the framework's library. Yii is flexible enough to allow us to use pieces of other frameworks. We will be using just one component of the Zend framework library, called Zend_Feed, so that we don't have to write all of the underlying "plumbing" code to generate our RSS formatted web feeds. For more on Zend_Feed, visit http://www.zendframework.com/manual/en/zend. feed.html

Installing Zend Framework

As we are using the Zend Framework to help support our RSS needs, we first need to download and install the framework. To get the latest version, visit http://framework.zend.com/download/latest. We will only be utilizing a single component of this framework, Zend_Feed, so the minimal version of the framework will suffice.

When you expand the downloaded framework file, you should see the following high-level folder and file structure:

INSTALL.txt 
LICENSE.txt 
README.txt 
bin/ 
library/

In order to use this framework within our Yii application, we need to move some of the files within our application's folder structure. Let's create a new folder under the /protected folder within our application called vendors/. Then, move the Zend Framework folder /library/Zend underneath this newly created folder. After everything is in place, ensure that protected/vendors/Zend/Feed.php exists in the TrackStar application.

Using Zend_Feed

Zend_Feed is a small component of the Zend Framework that encapsulates all of the complexities of creating web feeds behind a simple, easy-to-use interface. It will help us get a working, tested, RSS compliant data feed in place in very little time. All we will need to do is format our comment data in a manner expected by Zend_Feed, and it does the rest.

We need a place to house the code to handle the requests for our feed. We could create a new controller for this, but to keep things simple, we'll just add a new action method to our main CommentController.php file to handle the requests. Rather than add to the method a little at a time, we'll list the entire method here, and then talk through what it is doing.

Open up CommentController.php and add the following public method:

public function actionFeed()
{
    if(isset($_GET['pid'])) $projectId = intval($_GET['pid']);
    else $projectId = null;
 
    $comments = Comment::model()->findRecentComments(20, $projectId);
 
    //convert from an array of comment AR class instances to an  name=>value array for Zend
    $entries=array();
 
    foreach($comments as $comment)
    {
        $entries[]=array(
            'title'=>$comment->issue->name,
            'link'=>CHtml::encode($this->createAbsoluteUrl('issue/view',array('id'=>$comment->issue->id))),
            'description'=> $comment->author->username . 'says:' . 
                    $comment->content,'lastUpdate'=>strtotime($comment->create_time),
            'author'=>$comment->author->username,
        );
    }
 
    //now use the Zend Feed class to generate the Feed
    // generate and render RSS feed
    $feed=Zend_Feed::importArray(array(
            'title'=> 'Trackstar Project Comments Feed',
            'link'=> $this->createUrl(''),
            'charset' => 'UTF-8',    
            'entries' => $entries,
    ), 'rss');
 
    $feed->send();
 
}

This is all fairly simple. First we check the input request querystring for the existence of a pid parameter, which we take to indicate a specific project ID. Remember that we want to optionally allow the data feed to restrict the content to comments associated with a single project. Next we use the same method that we used in the previous iteration to populate our widget to retrieve a list of up to 20 recent comments, either across all projects, or if the project ID is specified, specific to that project.

You may remember that this method returns an array of Comment AR class instances. We iterate over this returned array and convert the data into the format expected by the Zend_Feed component. Zend_Feed expects a simple array containing elements which are themselves arrays containing the data for each comment entry. Each individual entry is a simple associative array of name=>value pairs. To comply with the specific RSS format, each of our individual entries must minimally contain a title, a link, and a description. We have also added two optional fields, one called lastUpdate, which Zend_Feed translates to the RSS field, pubDate, and one to specify the author.

There are a few extra helper methods we take advantage of in order to get the data in the correct format. For one, we use the controller's createAbsoluteUrl() method, rather than just the createUrl() method in order to generate a fully qualified URL. Using createAbsoluteUrl() will generate a link like the following http://localhost/trackstar/index.php?r=issue/view &id=5 as opposed to just /index.php?r=issue/view&id=5.

Also, to avoid errors such as "unterminated entity reference" being generated from PHP's DOMDocument::createElement(), which is used by Zend_Feed to generate the RSS XML, we need to convert all applicable characters to HTML entities by using our handy helper function, CHTML::encode. So, we encode the link such that a URL that looks like:

http://localhost/trackstar/index.php?r=issue/view&id=5

will be converted to:

http://localhost/trackstar/index.php?r=issue/view&id=5

Once all of our entries have been properly populated and formatted, we use Zend_ Feed's importArray() method which expects an array to construct the RSS feed. Finally, once the Zend feed class is built from the input array of entries and returned, we call the send() method on that class. This returns the properly formatted RSS XML and appropriate headers to the client.

We need to make a couple of configuration changes to the CommentController.php file and class before this will work. First, we need to import the /vendors/Zend/ Feed.php file as well as the Rss.php file under the Feed/ folder. Add the following statements to the top of CommentController.php:

Yii::import('application.vendors.*'); 
require_once('Zend/Feed.php'); 
require_once('Zend/Feed/Rss.php');

Then, alter the CommentController::accessRules() method to allow any user to access our newly added actionFeed() method:

public function accessRules()
{
    return array(
        array('allow',  // allow all users to perform 'index' and 'view' actions
            'actions'=>array('index','view', 'feed'),
             'users'=>array('*'),
        ),
    …

This is really all there is to it. If we now navigate to http://localhost/trackstar/ index.php?r=comment/feed, we can view the results of our effort. As browsers handle the display of RSS feeds differently, what you see might differ from the following screenshot. The following screenshot is what you should see you are if viewing the feed in the Firefox browser:

Creating user friendly URLs

So far, throughout the development process, we have been using the default format of our Yii application URL structure. This format, discussed back in Chapter 2, uses a querystring approach. We have the main parameter, 'r', which stands for route, followed by a controllerID/actionID pair, and then optional querystring parameters as needed by the specific action methods being called. The URL we created for our new feed is no exception. It is a long, cumbersome and dare we say ugly URL. There has got to be a better way! Well, in fact, there is.

We could make the above URL look cleaner and more self-explanatory by using the so-called path format, which eliminates the query string and puts the GET parameters into the path info part of URL:

Taking our comment feed URL as an example, instead of:

http://localhost/trackstar/index.php?r=comment/feed

we would have:

http://localhost/trackstar/index.php/comment/feed/

What's more, we don't even need to always specify the entry script for each request. We can also take advantage of Yii's request routing configuration options to remove the need to specify the controllerID/actionID pair as well. Our request could then look like:

http://localhost/trackstar/commentfeed

Also, it is common, especially with feed URL, to have the .xml extension specified at the end. So, it would be nice if we could alter our URL to look like:

http://localhost/trackstar/commentfeed.xml

This greatly simplifies the URL for users and is also an excellent format for URLs to be properly indexed into major search engines (often referred to as "search engine friendly URLs"). Let's see how we can use Yii's URL management features to alter our URL to match the desired format.

Using the URL manager

The built-in URL manager in Yii is an application component that can be configured in the protected/config/main.php file. Let's open up that file and add a new URL manager component declaration to the components array:

'urlManager'=>array( 
    'urlFormat'=>'path',
),

As long as we stick with the default and name it urlManager, we do not need to specify the class of the component because it is pre-declared to be CUrlManager.php in the CWebApplication.php framework class.

With this one simple addition, our URL structure has changed to the 'path' format throughout the site. For example, previously if we wanted to view, say, a specific issue whose ID is 1, we would make the request using the following URL:

http://localhost/trackstar/index.php?r=issue/view&id=1

but with these changes in place, our URL now looks like:

http://localhost/trackstar/index.php/issue/view/id/1

You'll notice the changes we have made have affected all the URLs generated throughout the application. To see this, visit our feed again by going to http://localhost/trackstar/index.php/comment/feed/. We notice that all of our issue links have been reformatted to this new structure for us. This is all thanks to our consistent use of the controller methods and other helper methods to generate our URLs. We can alter the URL format in just one single configuration file, and the changes will automatically propagate throughout the application.

Our URLs are looking better, but we still have the entry script, index.php, specified and we are not yet able to append the .xml suffix on the end of our feed URL. So, we'll hide the index.php as part of the URL, and also setup the request routing to understand that a request for commentfeed.xml actually means a request for the actionFeed() method within the CommentController.php class. Let's actually tackle the latter, first.

Configuring routing rules

Yii's URL manager allows us to specify rules that define how URLs are parsed and created. A rule consists of defining a route and a pattern. The pattern is used to match on the path information part of the URL to determine which rule is used for parsing or creating URLs. The pattern may contain named parameters using the syntax ParamName:RegExp. When parsing a URL, a matching rule will extract these named parameters from the path info and put them into the $_GET variable. When a URL is being created by the application, a matching rule will extract the named parameters from $_GET and put them into the path info part of the created URL. If a pattern ends with '/*', it means additional GET parameters may be appended to the path info part of the URL.

To specify URL rules, set the set the CUrlManager's rules property as an array of rules in the format pattern=>route.

As an example, let's look at the following two rules:

'urlManager'=>array( 
    'urlFormat'=>'path',
    'rules'=>array(
        'issues'=>'issue/index', 
        'issue/<id:\d+>/*'=>'issue/view', 
    )

There are two rules specified in the above code. The first rule says that if the user requests the URL http://localhost/trackstar/index.php/issues, it should be treated as http://localhost/trackstar/index.php/issue/index and the same applies when constructing such a URL.

The second rule contains a named parameter id which is specified using the syntax. It says that, for example, if the user requests the URL http://localhost/trackstar/index.php/issue/1, it should be treated as http://localhost/trackstar/index.php/issue/view?id=1. The same also applies when constructing such a URL.

The route can also be specified as an array itself to allow the setting of other attributes such as the URL suffix and whether or not the route should be considered as case sensitive. We'll take advantage of these as we specify the rule for our comment feed.

Let's add the following rule to our urlManager application component configuration:

'urlManager'=>array( 
    'urlFormat'=>'path',
    'rules'=>array(
        'commentfeed'=>array('comment/feed', 'urlSuffix'=>'.xml', 'caseSensitive'=>false),
    ),
),

Here, we have used the urlSuffix attribute to specify our desired URL .xml suffix.

Now we can access our feed by using the following URL: http://localhost/trackstar/index.php/commentFeed.xml

Removing the entry script from the URL

Now we just need to remove the index.php from the URL.This is done in two steps:

  • Alter the web server configuration to re-route all requests that don't correspond to existing files or directories to index.php.
  • Set the UrlManager's showScriptName property to false.

The first takes care of the how the application routes the requests, the second takes care of how URLs will be created throughout the application.

As we are using Apache HTTP Server, we can perform the first step by by creating a .htaccess file in the application root folder and adding the following directives to that file:

Options +FollowSymLinks 
IndexIgnore */* 
RewriteEngine on
 
# if a directory or a file exists, use it directly 
RewriteCond %{REQUEST_FILENAME} !-f 
RewriteCond %{REQUEST_FILENAME} !-d
 
# otherwise forward it to index.php 
RewriteRule . index.php

This approach is only for use with the Apache HTTP Server. You will need to consult your web server's re-write rules documentation if you are using a different web server. Also note that this information could be placed in the Apache configuration file as an alternative to using the .htaccess file approach.

With the .htaccess file in place, we can now visit our feed by navigating to http://localhost/trackstar/commentfeed.xml (or http://localhost/trackstar/commentFeed.xml as we set the case-sensitivity to false)

However, even with this in place, if we use one of the controller methods or one of our CHTML helper methods in our application to create our URL, say by executing the following in a controller class:

$this->createAbsoluteUrl('comment/feed');

it will generate the following URL, with index.php still in the URL: http://localhost/trackstar/index.php/commentfeed.xml

In order to instruct it to not use the entry script name when generating URLs, we need to set that property on the urlManager component. We do this again in the main.php config file as such:

'urlManager'=>array( 
    'urlFormat'=>'path', 
    'rules'=>array(
        'commentfeed'=>array('site/commentFeed', 'urlSuffix'=>'.xml', 'caseSensitive'=>false),
    ),
    'showScriptName'=>false,
),

In order to handle the addition of the project ID in the URL, which we need to restrict the comment feed data to comments associated with specific projects, we need to add one other rule:

'urlManager'=>array( 
    'urlFormat'=>'path',
    'rules'=>array(
        '<pid:\d+>/commentfeed'=>array('site/ commentFeed', 'urlSuffix'=>'.xml', 'caseSensitive'=>false),
        'commentfeed'=>array('site/commentFeed', 'urlSuffix'=>'.xml', 'caseSensitive'=>false),
    ), 
    'showScriptName'=>false,
),

This rule also uses the syntax to specify a pattern to allow for a project ID to be specified before the commentfeed.xml part of the URL. With this rule in place, we can restrict our RSS feed to comments specific to a project. For example, if we just want the comments associated with project # 2, the URL format would be: http://localhost/trackstar/2/commentfeed.xml

Adding the feed links

Now that we have created our feed and altered the URL structure to make it more user and search engine friendly, we need to add the ability for users to subscribe to the feed. One way to do this is to add the following code before rendering the pages in which we want to add the RSS feed link. Let's do this for both the project listing page as well as a specific project details page. We'll start with the project listings page. This page is rendered by the ProjectController::actionIndex() method. Alter that method as such:

public function actionIndex() 
{
    $dataProvider=new CActiveDataProvider('Project');
    Yii::app()->clientScript->registerLinkTag(
        'alternate', 
        'application/rss+xml', 
        $this->createUrl('comment/feed'));
    
    $this->render('index',
        array( 'dataProvider'=>$dataProvider,)
    );
}

The above highlighted code adds the following to the <head> rendered HTML:

<link rel="alternate" type="application/rss+xml" href="/commentfeed. xml" />

In many browsers, this will automatically generate a little RSS feed icon in the address bar. The following screenshot depicts what this icon looks like in the Firefox 3.6 address bar:

We make a similar change to add this link to a specific project details page. The rendering of these pages is handled by the ProjectController::actionView() method. Alter that method to be the following:

public function actionView() 
{
    $issueDataProvider=new CActiveDataProvider('Issue', array(
        'criteria'=>array(
            'condition'=>'project_id=:projectId', 
            'params'=>array(':projectId'=>$this->loadModel()->id), 
        ),
        'pagination'=>array( 
            'pageSize'=>1,
        ),
    ));
 
    Yii::app()->clientScript->registerLinkTag( 
        'alternate',
        'application/rss+xml',
        $this->createUrl('comment/feed',array('pid'=>$this->loadModel()->id)));
 
    $this->render('view',array( 
        'model'=>$this->loadModel(), 
        'issueDataProvider'=>$issueDataProvider,
    ));
}

This is almost the same as what we added to the index method, except that we are specifying the project ID so that our comment entries are restricted to just those associated with that project. A similar icon will now display in the address bar on our project details page. Clicking on one of these icons allow the user to subscribe to these comment feeds.

Summary

This iteration demonstrated just how easy it is to integrate Yii with other external frameworks. We specifically used the popular Zend Framework to demonstrate this and were able to quickly add an RSS compliant web feed to our application. Though we specifically used Zend_Feed, we really demonstrated how to integrate any of the Zend Framework components into the application. This further extends the already extensive feature offering of Yii, making Yii applications incredibly feature rich.

We also learned about the URL Management features within Yii and altered our URL format throughout the application to be more user and search engine friendly. This is a first step in improving upon the look and feel of our application. Something we have very much neglected up to this point. Turning our focus to styles, themes, and generally making things pretty is the focus of the next iteration.

评论 X

      友荐云推荐
      Copyright 2011-2014. YiiBook.com