Understanding the Zend Framework, Part 6: Sending e-mail

Earlier installments in this "Understanding the Zend Framework" series use the PHP Zend Framework to create the basic Chomp online feed reader. Now it's time to add e-mail to the mix. This article explains how to use the Zend_Mail component to send text and HTML e-mail alerts to users when new content has been added to subscribed feeds.

Share:

Gina Deol (gdeol@binaryits.com), Vice President, E-commerce Development, Binary IT Solutions

Gina Deol is in her last year as a bachelor's of science student in computer science and accounting information systems at Sacramento State University. She has worked as a Web development engineer and project coordinator, and she is currently vice president of e-commerce development for Binary IT Solutions, based in Sacramento.



18 January 2011 (First published 15 August 2006)

Prerequisites

The sample application used in this series is built using V.1.10.6 of the Zend Framework for PHP. Prerequisites for the Zend Framework include PHP V5.2 and the Apache Web server. For detailed installation instructions, see Part 2.

To follow along, you should have a basic idea of programming in PHP. You should also have the code from earlier parts of the "Understanding the Zend Framework" series.


Introduction

Part 1 talked about the overall concepts of the Zend Framework, including a list of relevant classes and a general discussion of the MVC pattern. Part 2 expanded 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 dealt 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 explained 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 explained how to use the Zend_PDF module to enable the user to create a customized PDF of saved articles, images, and search results. Here in Part 6, you use the Zend_Mail module to alert users to new posts. In Part 7, you will look at searching saved content and returning ranked results. In Part 8, you will create your own mash-up, adding information from Amazon, Flickr, Twitter and Yahoo! And in Part 9, you will add Ajax interactions to the site using JavaScript object notation.

In this article, you will be sending two types of e-mail messages. The first is a simple text-based e-mail that simply lists the feeds to which a user has subscribed that have been updated since the user last read them. The second is an e-mail that actually sends the new posts that have arrived since the last time the user read a particular feed.

You will first update the database so every feed has an accurate last-updated value. You'll also update subscribed feeds with a last-pulled value when the reader views them. Finally, you'll create the routine that compares these dates, creates the e-mail messages, and sends them.


A brief introduction to e-mail

An e-mail message's structure is composed of the message header and the message body. The message header includes the information about the message, such as the receiver, sender, date, message ID, sender server IP address, routing information, and the Multipurpose Internet Mail Extensions (MIME) type. The MIME type identifies the message type as being a simple text message, HTML message, or message with attachment(s). The message body is simply the text of the message that can extend over several lines and may contain attachments.

E-mail is sent via mail transfer agents, mail delivery agents, and mail user agents. The mail transfer agent is also known as the mail server and is simply software that sends e-mail from one computer to another. The mail transfer agent receives the messages from another mail transfer agent, a mail user agent, or a mail submission agent. The mail delivery agent is a computer program that accepts incoming messages and forwards them to a destination on a remote server called a Simple Mail Transfer Protocol (SMTP) server. If the destination account is on a local machine, the mail delivery agent distributes the messages directly to the intended recipient's mailbox. A mail user agent is software used to read and send messages, such as Mozilla Thunderbird or Microsoft® Outlook®.


The Zend_Mail component

The Zend_Mail component of the Zend Framework comes with some very important features, such as the ability to add multiple recipients or to send multiple e-mail messages on one SMTP connection. Zend_Mail can also send HTML e-mail or create multi-part e-mail messages for providing a text alternative to HTML. You can also send advanced messages with MIME attachments, custom mail headers, and controlled character sets. Zend_Mail comes with support for 8-bit, base64, binary, quotable-printable encoding, and alternative transports, such as POP3 and IMAP, as well as the ability to read received e-mail.

The Zend_Mail component includes:

  • Zend_Mail_Message
  • Zend_Mail__Storage_Mbox
  • Zend_Mail_Storage_POP3
  • Zend_Mail_Protocol_POP3
  • Zend_Mail_Storage_Maildir
  • Zend_Mail_Protocol_Imap
  • Zend_Mail_Storage_Imap
  • Zend_Mail_Transport_Imap

Database changes

Here you need to add a lastpulled column to the subscribedfeeds table and a lastupdated to the feeds table. You are doing this to help manage the age of the content being pulled into the feed and subscribedfeeds tables. To do that, start the MySQL console and type the following:

alter table feeds add lastupdated datetime;
alter table subscribedfeeds add lastpulled datetime;

Modification in FeedController->viewChannelAction()

To handle the fields you've added to the database you make a change in the existing code. In FeedController->viewChannelAction(), you will update the lastpulled field with the current date to keep the date when it is viewed last by the current user (see Listing 1).

Listing 1. Updating the lastpulled value
<?php
    public function viewChannelAction()
    {
	     $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('title');

        $filterSession = Zend_Registry::get('fSession');
        $username = $filterSession->getRaw('username');

        $filterGet = Zend::registry('fGet');
        $feedTitle = $filterGet->getRaw('title');

        $db = Zend_Registry::get('db');
        $select = $db->select();
        $select->from('feeds', '*');
        $select->where('feedname=?', $feedTitle);
        $results = $db->fetchAll($select);

        $curDate = date("Y-m-d H:i:s")
        $table = 'subscribedfeeds';
        $row = array(
             'lastpulled' => $curDate
             );

        $where = "username='$username' and feedname='$feedTitle'";
        $rowsAffected = $db->update($table, $row, $where);

        $feedLink = $results[0]['link'];
        $rssFeed = Zend_Feed::import($feedLink);
        
        $view = Zend::registry('view');
        $view->title = $feedTitle;
        $view->rssFeed = $rssFeed;
        echo $view->render('viewChannel.php');
    }
?>

To update the subscribedfeeds table, you need to know the username for the logged-in user, so you grab that from the session. Next, you create a string for the current date and use it to create a row of information to be used to update the subscribedfeeds table. Notice that a row used in an update does not have to contain all of the columns in the table; only those included will be used to update the table. You then create the query (in other words, the where clause) and execute the update.


Updating feed data

The next step is to make sure you can tell when a feed was last updated. To do that, create a new PHP file called sendUpdates.php. (It may be simplest to place the file in your <ZEND_HOME>/library directory.) Your first instinct may be to place the functionality in the IndexController, but it is better as a separate file for two reasons: First, you'll want to execute the file on a regular basis, such as once a day, using cron, Windows scripting host, or a similar scheduling technology. Second, taking it out of the Web application means you won't have to worry about unauthorized Web users executing it.

Start by searching the database for all RSS- or Atom-type feeds (as opposed to HTML pages) as shown in Listing 2.

Listing 2. Finding the feeds
<?php
include 'Zend.php';

function __autoload($class)
{
    Zend_Loader::loadClass($class);
}

$params = array ('host' => 'localhost',
                 'username' => 'chompuser',
                 'password' => 'chomppassword',
                 'dbname'   => 'chomp');
$db = Zend_Db::factory('pdoMysql', $params);

$select = $db->select();
$select->from('feeds', '*');
$select->where("rss = 'true'");
$results = $db->fetchAll($select);
foreach($results as $row)
{
    echo $row['link']."\n";
}
?>

You connect to the database and creative execute the query. For each feed, you output the URL. To execute this file, go to the command line, change to the directory in which you saved the file, and type php sendUpdates.php (see Listing 3).

Listing 3. Getting the dates
<?php
include 'Zend.php';

function __autoload($class)
{
    Zend_Loader::loadClass($class);
}

$params = array ('host' => 'localhost',
                 'username' => 'chompuser',
                 'password' => 'chomppassword',
                 'dbname'   => 'chomp');
$db = Zend_Db::factory('pdoMysql', $params);

$select = $db->select();
$select->from('feeds', '*');
$select->where("rss = 'true'");
$results = $db->fetchAll($select);
foreach($results as $row)
{
    echo $row['link']."\n";
    $lastUpdated = null;

    try {
        $thisFeed = Zend_Feed::import($row['link']);
        $lastUpdated = null;

        if ($thisFeed->pubDate())
        {
            //echo "Feed last updated at ".$thisFeed->pubDate()."\n";
            $lastUpdated = $thisFeed->pubDate();
        } 
        else 
        {
            if (isset($thisFeed[0]))
            {
                $firstItem = $thisFeed[0];
                if ($firstItem->pubDate()){
                    //echo "Most recent item on
 ".$firstItem->pubDate()."\n";
                    $lastUpdated = $firstItem->pubDate();
                } 
                else 
                {
                    if ($firstItem->published())
                    {
                        //echo "Most recent atom item on
 ".$firstItem->published()."\n";
                        $lastUpdated = $firstItem->published();
                    } 
                    else 
                    {
                        //echo "No date information available.\n";
                        $lastUpdated = null;
                    }
                }
            }
        }
    } 
    catch (Zend_Feed_Exception $e) 
    {
        //echo "Exception caught importing feed: {$e->getMessage()}\n";
    }

    //echo "Last updated is ".$lastUpdated."<br />";
    if (isset($lastUpdated)){
        $lastUpdated = strtotime($lastUpdated);
        $lastUpdated = date('Y-m-d H:i:s', $lastUpdated);
    }
    $table = 'feeds';
    $updateRow = array('lastupdated' => $lastUpdated);
    $where = "link = '".$row['link']."'";
    $rowsAffected = $db->update($table, $updateRow, $where);
}
?>

First, you're loading the actual XML feed. Assuming you're dealing with RSS, you first check for an overall pubDate for the feed. Because this value is optional, you may need to fall back on checking the most recently posted item for its pubDate. Finally, if this is an Atom feed, you'll request the published date instead. Once you have the day, you can extract it in the appropriate format and update the row in the database.


Finding updated feeds

Now that you know each field has been updated, you can send users e-mail messages, letting them know if their favorite feeds have been updated. To do this, you will compare the lastupdated field for table feeds and the lastpulled field of table subscribedfeeds, and send e-mail messages to users for newer feeds. Add the code in Listing 4 to sendUpdates.php.

Listing 4. Finding updated fields
...
    $table = 'feeds';
    $updateRow = array('lastupdated' => $lastUpdated);
    $where = "link = '".$row['link']."'";
    $rowsAffected = $db->update($table, $updateRow, $where);
}
$select = $db->select();
$select->from('users, subscribedfeeds, feeds', '*');
$select->where('users.Username=subscribedfeeds.username');
$select->where('feeds.feedname=subscribedfeeds.feedname');
$select->where('feeds.lastupdated > subscribedfeeds.lastpulled');
$results = $db->fetchAll($select);
foreach($results as $row)
{
    $email = $row['emailaddress'];
    $fName = $row['firstname'];
    $lName = $row['lastname'];
    $feedName = $row['feedname'];

    //send e-mail here
}
?>

You are fetching data from the database after comparing the lastupdated field for table feeds and the lastpulled field of table subscribedfeeds.

Now it's time to actually send the e-mail messages.


Sending messages

Zend_Mail, supported by Zend_Mime, creates and sends e-mail messages with a text or HTML format. Zend_Mime is a support set for handling multipart MIME messages. It is used not only by Zend_Mail but also by any other applications requiring MIME support. Zend_Mime has a set of methods specified to work with MIME:

  • boundary(), which returns the MIME boundary string, used to separate parts of the message
  • boundaryLine(), which returns the complete MIME boundary line
  • encodeBase64(), which encodes a string into base64 encoding
  • encodeQuotablePrintable(), which encodes a string with quotable-printable mechanism
  • isPrintable(), which returns TRUE if the given string contains no unprintable characters and returns FALSE if the given string does contain printable characters
  • mimeEnd(), which returns the complete MIME end boundary line

Zend_Mail allows e-mail to be sent in an easy form while preventing mail injection. Zend_Mail messages can be sent via SMTP, so Zend_Mail_Transport_SMTP needs to be created and registered with Zend_Mail before the send() method is called. Add the code in Listing 5 to sendUpdates.php.

Listing 5. Sending the e-mail messages
...
    $table = 'feeds';
    $updateRow = array('lastupdated' => $lastUpdated);
    $where = "link = '".$row['link']."'";
    $rowsAffected = $db->update($table, $updateRow, $where);
}

require_once 'Zend/Mail/Transport/Smtp.php';
$tr = new
Zend_Mail_Transport_Smtp('mail.example.com');
Zend_Mail::setDefaultTransport($tr);
$select = $db->select();
$select->from('users, subscribedfeeds, feeds', '*');
$select->where('users.Username=subscribedfeeds.username');
$select->where('feeds.feedname=subscribedfeeds.feedname');
$select->where('feeds.lastupdated > subscribedfeeds.lastpulled');
$results = $db->fetchAll($select);
foreach($results as $row)
{
    $email = $row['emailaddress'];
    $fName = $row['firstname'];
    $lName = $row['lastname'];
    $feedName = $row['feedname'];

    $mail = new Zend_Mail ();
    $mail->setFrom ('alerts@chomp.backstopmedia.com', 'Chomp! Alerts');
    $mail->addTo ($email, "$fName $lName");
    $mail->setSubject ('Your feeds have been updated');
    $mail->setBodyText ("One of your subscribed feeds, $feedName, ".
                        "has been updated.");
    $mail->send ();
}
?>

The first step is to set the transport for the message. This enables you to specify a different SMTP server than the one listed in your PHP.ini file. Next, you create the actual Zend_Mail object and populate it. Here, you are setting the to and from values, as well as the subject and body. In this case, you are specifying only a text body. In a moment, you will look at sending HTML and multipart messages.

Finally, you actually send the message. Note that the way this routine is structured, you are actually sending an e-mail for each feed that has been updated. Obviously, this would not be convenient for users who have multiple feeds.

Let's look at sending only one e-mail per user and how to use HTML capabilities to send individual item links.


Sending HTML e-mail messages

The final step in your updates routine is to change the process so that rather than sending a user a new e-mail for every feed that has been updated, you send only one e-mail if any of the feeds have been updated. You'll also add links to the actual items, themselves.

Start by changing the single query to two queries, with the outside query looping through each user (see Listing 6.

Listing 6. Breaking it up by user
...
    $where = "link = '".$row['link']."'";
    $rowsAffected = $db->update($table, $updateRow, $where);
}

require_once 'Zend/Mail/Transport/Smtp.php';
$tr = new
Zend_Mail_Transport_Smtp('mail.example.com');
Zend_Mail::setDefaultTransport($tr);

$userSelect = $db->select();
$userSelect->from('users', '*');
$userResults = $db->fetchAll($userSelect->__toString());
foreach ($userResults as $thisUserRow){

    $thisUser = $thisUserRow['username'];
    $email = $thisUserRow['emailaddress'];
    $fName = $thisUserRow['firstname'];
    $lName = $thisUserRow['lastname'];

    $select = $db->select();
    $select->from('subscribedfeeds, feeds', '*');
    $select->where("subscribedfeeds.username='$thisUser'");
    $select->where('feeds.feedname=subscribedfeeds.feedname');
    $select->where('feeds.lastupdated > subscribedfeeds.lastpulled');
    $results = $db->fetchAll($select->__toString());

    $bodytext = "";

    foreach($results as $row)
    {

        $feedName = $row['feedname'];
        $bodytext = $bodytext . "One of your subscribed feeds, " .
                    $feedName . ", "has been updated.\n";

    }

    $mail = new Zend_Mail ();
    $mail->setFrom ('alerts@chomp.backstopmedia.com', 'Chomp! Alerts');
    $mail->addTo ($email, "$fName $lName");
    $mail->setSubject ('Your feeds have been updated');
    $mail->setBodyText ($bodytext);
    $mail->send ();

}
?>

You retrieve all of the user information and key the feed search off this user. You then move the actual sending of the mail outside this loop, instead building a string of text to add to the body of the message. When the script has looked at all of the feeds for this user, it creates the e-mail and sends it off.

Now you just have to add the actual feed items (see Listing 7).

Listing 7. Adding the feed items
...
    $select->where('feeds.lastupdated > subscribedfeeds.lastpulled');
    $results = $db->fetchAll($select->__toString());

    $bodytext = "";
    $bodyhtml = "";

    foreach($results as $row)
    {

        $feedName = $row['feedname'];
 
        $bodytext = $bodytext . '\n' . $feedName . " has new items:\n\n";
        $bodyhtml = $bodyhtml . '<p>' . $feedName . " has new
 items:</p>";

        $thisFeed = Zend_Feed::import($row['link']);
        foreach($thisFeed as $thisItem){
           $thisItemDate = null;
           if ($thisItem->pubDate())
           {
               $thisItemDate = $thisItem->pubDate();
           } 
           else 
           {
               $thisItemDate = $thisItem->published();
           } 
           if ($thisItemDate > $row[lastpulled]){
               $bodytext = $bodytext . $row['feedname'].'\n';
               $bodyhtml = $bodyhtml . '<a
 href="'.$row['link'].'">'.$row['feedname'].'</a><br />';
           }
        }
    }

    $mail = new Zend_Mail ();
    $mail->setFrom ('alerts@chomp.backstopmedia.com', 'Chomp! Alerts');
    $mail->addTo ($email, "$fName $lName");
    $mail->setSubject ('Your feeds have been updated');
    $mail->setBodyText ($bodytext);
    $mail->setBodyHtml ($bodyhtml);

    $mail->send ();

}
?>

In this case, you create a message that sends a text and an HTML version of the message, so you create variables to hold both strings. Then, for each feed, you import the feed, looping through each of its items to see if any were posted after the last time the user read the feed, as defined by the lastpulled column. If any items satisfy this qualification, you add them to the body of the message. For the text body, you add only the name, while you also add a link to the item for the HTML version.

When you are ready to send the actual message, you populate the text as you did before, but you also populate the HTML version using the setBodyHtml() method.


Summary

This article discussed how you can use the Zend_Mail module for a variety of functions. You used it to alert users to new posts in their subscribed feeds and to send those posts using HTML e-mail. In a production application, you will likely find many uses for this capability, such as sending out password reminders and periodic subscriber mailings.

In Part 7 of this "Understanding the Zend Framework" series, you will look at using the Zend Framework's search capabilities.


Download

DescriptionNameSize
Part 6 source codeos-php-zend6.zip4KB

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=153263
ArticleTitle=Understanding the Zend Framework, Part 6: Sending e-mail
publish-date=01182011