API development

All the work on this chapter will be done on the API side of the project and no changes are required on the client. In this way, we can manage exactly what we are sending, the recipient, and so on.

Requirements

We have a few requirements for this section of the API, which are as follows:

  • We need to centralize all the e-mail logic on one class
  • The e-mails must support HTML
  • The HTML must not be mixed with the PHP code and they have to be placed on a template
  • The e-mails must use a common layout to simplify the changes on the skeleton of the e-mail.

The module structure

As the code will be used in different types of controllers, we are going to add the code to the Common module. The following screenshot shows how the folder structure will look, along with the new files:

The module structure

Adding the mailer.php file

This is the class where we are going to centralize all the code related to the e-mail sending functionality and this is also the class that we have to change every time we want to add a new e-mail to the system. The structure is very simple, we will have a bunch of specialized methods that will take care of the information of a single e-mail type. Then we will have a method to initialize some common code, and finally a method that will take care of sending the actual e-mail. The following are the contents of the mailer.php file:

namespace Common;

use ZendMailMessage;
use ZendMailTransportSendmail;
use ZendViewRendererPhpRenderer;
use ZendViewResolverTemplateMapResolver;
use ZendViewModelViewModel;
use ZendMimeMessage as MimeMessage;
use ZendMimePart as MimePart;

These are the initial lines of the class. As usual, we have declared the namespace and imported the components we need to use. The first three use statements refer to the Mail components.

There are different ways of sending e-mails you can deliver them locally if the e-mail accounts are stored on the same server on which your app is running, you can relay them to another server to send it, or you can send it from your server using some sort of mail server. In our case, the virtual machine provided with the book has postfix installed that allows us to send e-mails using a program called sendmail. ZF2 provides different transport objects that do the actual job of interfacing the PHP code with the sending program to send the e-mail; that's why, in this case, we import the ZendMailTransportSendmail component.

The next three lines are used to import the View components we are going to use to process the HTML code of the e-mails.

The final two lines are used together with the Message component to be able to specify the HTML content of the e-mail, otherwise the contents of the e-mail will be treated as plain text, and the e-mail programs will not read the HTML code. We can define a class using the following code:

class Mailer

As you can see, the class declaration is really simple; in this case we are not extending from another class or implementing an interface.

The following is the first specialized method we are going to see:

public static function sendContentNotificationEmail(
    $recipientAddress, $recipientName, $authorName
){
    $subject = 'New comment on your wall!';
    $templateVars = array(
        'recipientName' => $recipientName,
        'authorName' => $authorName
    );
    
    self::send(
        $recipientAddress, 
        $recipientName, 
        $subject, 
        'NewComment', 
        $templateVars
    );
}

In this case, the sendContentNotificationEmail() method takes care only of the content notification e-mail. This e-mail is sent every time a person posts a comment on the user's wall.

As you can see, we expect the name and e-mail address of the recipient and the name of the author of the comment. After that, we store the subject of the e-mail on a variable and we build an array with the variables we want to pass to the template of the e-mail.

Finally, we call another method called send(), which will take care of sending the e-mail. The parameters we need to pass to the send() method are the recipient's address, the recipient's name, the subject of the e-mail, the name of the template we want to use, and the variables we want to pass to the template. Later, we will see how the name of the template is mapped to the actual filename of the template. The following is the sendWelcomeEmail() method:

public static function sendWelcomeEmail(
    $recipientAddress, $recipientName
){
    $subject = 'Welcome to My Social Network';
    $templateVars = array(
        'recipientName' => $recipientName
    );
    
    self::send(
        $recipientAddress, 
        $recipientName, 
        $subject, 
        'WelcomeTemplate', 
        $templateVars
    );
}

The sendWelcomeEmail method will take care of sending the welcome e-mail to the user when they create an account. As you can see, this is very similar to the one we saw before, and expects the e-mail address and the name of the recipient. The initResolver() method is as follows:

protected static function initResolver()
{
    $resolver = new TemplateMapResolver;
    $resolver->setMap(array(
        'MailLayout' 
             => __DIR__ . '/../../view/layout/email-layout.phtml',
        'WelcomeTemplate' 
             => __DIR__ . '/../../view/emails/welcome.phtml',
        'NewComment' 
             => __DIR__ . '/../../view/emails/new-comment.phtml',
    ));
    
    return $resolver;
}

The preceding code takes care of initializing a component we will use on the send() method. The code itself initializes a TemplateMapResolver object that maps keys to the path of a file. It is in this code that we are mapping the names of our templates with the actual path and filename of the template, and this allows us to avoid inserting paths everywhere to specify the template we want to use. As you can see in the preceding code, we also specify the layout we are going to use on the e-mails.

If, in the future, you need to add some more e-mails to the social network, you will need to map here the name of the new template with the path of the file containing the HTML message.

Now, let's see the contents of the last method of this class. It is in the send() method, where we are going to glue all the pieces together in order to send the e-mail to the recipient's inbox. The send() method is as follows:

protected static function send(
    $toAddress, 
    $toName, 
    $subject, 
    $templateName, 
    $templateVars = array()
){
    $view = new PhpRenderer;
    $view->setResolver(self::initResolver());
    
    $viewModel = new ViewModel;
    $viewModel
        ->setTemplate($templateName)
        ->setVariables($templateVars);
    $content = $view->render($viewModel);
    
    $viewLayout = new ViewModel;
    $viewLayout->setTemplate('MailLayout')->setVariables(array(
        'content' => $content,
    ));
    
    $html = new MimePart($view->render($viewLayout));
    $html->type = "text/html";
    
    $body = new MimeMessage();
    $body->setParts(array($html));
    
    $mail = new Message;
    $mail->setBody($body);
    $mail->setFrom('[email protected]', 'My social network'),
    $mail->addTo($toAddress, $toName);
    $mail->setSubject($subject);
    
    $transport = new Sendmail;
    $transport->send($mail);
}

As you can see, the preceding method expects the parameters we listed before. The first two lines of code instantiates a PhpRenderer component. This is used because our HTML templates will include the PHP code in order to insert some useful information, such as the name of the recipient or the name of the author who posted the content on the wall.

After that, we create a ViewModel instance and we set the template of the model to the template name passed as a parameter, and we also pass the template variables to replace them in the HTML code. Right after that, we will render the code using the PhpRenderer object and store it in a file. At this point, PhpRenderer is using the TemplateMapResolver object we configured before in order to translate the template name passed as a parameter to the actual path of the file.

The next section of code is going to render the layout of the e-mail passing the content of the e-mail as the content template variable.

Now we have a ViewModel object that contains the layout and the injected HTML code from the e-mail message creating a longer HTML code.

We now jump to create the e-mail itself. Nowadays, e-mail messages are of the multipart type, which means that we can use a part of the text/plain type to specify the contents of the e-mail as plain text and another part of the text/html type to specify the HTML version of the message.

As we are dealing with HTML, we need to specify the text/html part on the e-mail and that's what we are doing in the next section. The content of the MimePart object is the HTML we had stored on the $viewLayout variable we created before. Remember that this variable contains not only the layout, but also the contents of the e-mail.

As this is a message that will contain the HTML code, we need to create a Message class that supports the feature we are trying to use and that's why we create an MimeMessage object.

After setting the HTML part on the MimeMessage object, we will create the final e-mail object instantiating the Message class. We set the body, the sender's name and address, the recipient's name and address, and the subject of the e-mail.

Finally, we will create an instance of the Sendmail transport and use it to send the e-mail to the Internet.

The email-layout.phtml file

As usual, we are not going to paste the HTML code here because it is long, verbose, and will not be readable at all; instead, we are just going to highlight one thing. As you saw before, we will inject the content of the e-mail to the layout using a variable called $content.

As this file will be parsed by the PhpRender object, we can use the PHP code and that's how we are going to merge the HTML code of this file with the HTML code injected in the send() method. The specific line has no mystery, it is similar to the following one:

<?php echo $content ?>

This is how we are usually going to insert the content on the e-mail. In this case, it is the whole message, but we are going to see in the next section how we can insert the name of the user.

The view welcome.php file

The view welcome.php file contains the code we use when we send a message to the user who just created an account. The text is pretty lame and we just say hello. However, as an example, it is enough.

The e-mail itself is customized with the name of the user and this is as simple as echoing the variable sent by the method that handles the following e-mail message, if you want to try to add more contents on the e-mail and insert more data of the user and make it more friendly:

Hi <?php echo $recipientName ?>

The view new-comment.phtml file

When a user posts a comment, the owner of the wall receives an e-mail notifying about what just happened. This e-mail message is still lame but it contains the author name of the comment. The message will contain a line similar to the following one:

Hi <?php echo $recipientName ?>,
<?php echo $authorName ?> has posted a new comment on your wall!

Modifying the IndexController.php file

Now that we have configured the mail system in place, we need to start calling the system on specific situations to send a proper e-mail.

We are going to add just one line in the Users module of the IndexController file in order to send an e-mail when the account of the user is created. The code must be placed right after the block of code where we know for sure that the account has been created without problems.

Mailer::sendWelcomeEmail($user['email'], $user['name']);

The line should look similar to the preceding line. As we created static methods on the Mailer class, we don't need to create an instance. Remember, that in order to use this class, you must import it at the beginning of the controller.

The IndexController.php file on the Wall module

The code we need to add to this controller to notify the user about the new content on his wall is a little bit trickier due to the structure of the tables and the way in which the data is stored.

When a request is made to this API endpoint, we know the user ID of the current author but not the user ID of the previous author who made the original post on which is being commented. As our e-mail has to go to the owner of the original content, we need to find out who that user is.

To make everything a little bit uglier, we can comment on three different types of contents, which means that we need to create an instance of a table gateway based on the type of the contents that is being commented.

In the method we have a switch structure that we use to set the $validatorTable value, and this is where we are going to extract the original entry based on the type of content. The line we have to add on each case of the switch statement looks similar to the following one:

$table = $this->getUserStatusesTable();

Of course, we will change the method call on each case to create an instance of the corresponding table gateway.

After that we can safely get the entry and the author of the original entry using the following code:

$entry = $table->getById($data['entry_id']);
$recipient = $usersTable->getById($entry['user_id']);

As you can see, we use the table gateway to get a row based on the ID. You will notice that two of the three table gateways do not have the getById() method but they are pretty simple to implement. At this point of the app, because some methods on the table gateways are duplicated on multiple files, is a good moment to create a base table gateway file and move the common code to the parent class and extend the children from the new base table gateway. You should be confident enough to jump and do it in case if you didn't refactor the table gateways before.

Finally, after we know that the comment was created successfully, we can safely send the message to the user using the following lines of code:

if($creationResult && $recipient['id'] != $user['id']) {
    Mailer::sendContentNotificationEmail(
        $recipient['email'], $recipient['name'], $user['name']
    );
}

Can you explain why we are limiting the sending of the e-mail, where the recipient ID and the user ID are different?

The challenge

We are near to the end of the book, and we saw a lot of things, we learned about most of the ZF2 components and we created a basic social network from scratch. At this point, I want to test your skills and measure how much you have learned so far by challenging you. Of course, this will also help you to revise any part of the book to reinforce your knowledge and master ZF2.

I want you to implement a whole new functionality on the social network that will touch almost all the topics we saw so far. The users can now register and log in to the social network, they can post content and read their favorite RSS feeds, but what happens if they forget the password? How can they recover it?

In order to help them, the challenge is to build the forgotten password system we will usually find on almost all the applications out there. The following are the requirements for this:

  • A new link should be placed on the login screen to allow the user to go to the recover password page.
  • The recover password page should show to the user a new form where they can enter their e-mail address.
  • The system should send an e-mail to the user with a unique token assigned to their account and a link pointing them to the recover page; the link should contain their token.
  • After clicking on the link in the e-mail, the system must show the user a new form to allow them to change their password by specifying the new password twice.
  • After the submission of the form, the clients have to contact the API to notify about the change of the password. Of course, the API will require a new endpoint for this and the new password should be encrypted using bcrypt.
  • Once the password has been changed, the unique token for this user should be removed and the user should be redirected to the login page.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.252.140