User Interaction and Email Automation in Symfony 1.3: Part2

Exclusive offer: get 50% off this eBook here
Symfony 1.3 Web Application Development

Symfony 1.3 Web Application Development — Save 50%

Design, develop, and deploy feature-rich, high-performance PHP web applications using the Symfony framework

£14.99    £7.50
by Tim Bowler | September 2009 | MySQL Open Source PHP Web Development

Read Part One of User Interaction and Email Automation in Symfony 1.3 here.

Automated email responses

Symfony comes with a default mailer library that is based on Swift Mailer 4, the detailed documentation is available from their web site at http://swiftmailer.org.

After a user has signed up to our mailing list, we would like an email verification to be sent to the user's email address. This will inform the user that he/she has signed up, and will also ask him or her to activate their subscription.

To use the library, we have to complete the following three steps:

  1. Store the mailing settings in the application settings file.
  2. Add the application logic to the action.
  3. Create the email template.

Adding the mailer settings to the application

Just like all the previous settings, we should add all the settings for sending emails to the module.yml file for the signup module. This will make it easier to implement any modifications required later. Initially, we should set variables like the email subject, the from name, the from address, and whether we want to send out emails within the dev environment. I have added the following items to our signup module's setting file, apps/frontend/config/module.yml:

dev:
mailer_deliver: true
all:
mailer_deliver: true
mailer_subject: Milkshake Newsletter
mailer_from_name: Tim
mailer_from_email: no-reply@milkshake

All of the settings can be contained under the all label. However, you can see that I have introduced a new label called dev. These labels represent the environments, and we have just added a specific variable to the dev environment. This setting will allow us to eventually turn off the sending of emails while in the dev environment.

Creating the application logic

Triggering the email should occur after the user's details have been saved to the database. To demonstrate this, I have added the highlighted amends to the submit action in the apps/frontend/modules/signup/actions/actions.class.php file, as shown in the following code:

public function executeSubmit(sfWebRequest $request)
{
$this->form = new NewsletterSignupForm();
if ($request->isMethod('post') && $this->form->
bindAndSave($request->getParameter($this->form->
getName())))
{
//Include the swift lib
require_once('lib/vendor/swift-mailer/lib/swift_init.php');
try{
//Sendmail
$transport = Swift_SendmailTransport::newInstance();
$mailBody = $this->getPartial('activationEmail',
array('name' => $this->form->getValue('first_name')));
$mailer = Swift_Mailer::newInstance($transport);
$message = Swift_Message::newInstance();
$message->setSubject(sfConfig::get('app_mailer_subject'));
$message->setFrom(array(sfConfig::
get('app_mailer_from_email')

=> sfConfig::get('app_mailer_from_name')));
$message->setTo(array($this->form->getValue('email')=> $this->
form->getValue('first_name')));
$message->setBody($mailBody, 'text/html');
if(sfConfig::get('app_mailer_deliver'))
{
$result = $mailer->send($message);
}
}
catch(Exception $e)
{
var_dump($e);
exit;
}
$this->redirect('@signup');
}
//Use the index template as it contains the form
$this->setTemplate('index');
}

Symfony comes with a sfMailer class that extends Swift_Mailer. To send mails you could simply implement the following Symfony method:
$this->getMailer()->composeAndSend('from@example.com',
'to@example.com', 'Subject', 'Body');

Let's walk through the process:

  1. Instantiate the Swift Mailer.
  2. Retrieve the email template (which we will create next) using the $this->getPartial('activationEmail', array('name' => $this->form->getValue('first_name'))) method. Breaking this down, the function itself retrieves a partial template. The first argument is the name of the template to retrieve (that is activationEmail in our example) which, if you remember, means that the template will be called _activationEmail.php. The next argument is an array that contains variables related to the partial template. Here, I have set a name variable. The value for the name is important. Notice how I have used the value within the form object to retrieve the first_name value. This is because we know that these values have been cleaned and are safe to use.
  3. Set the subject, from, to, and the body items. These functions are Swift Mailer specific:
    • setSubject(): It takes a string as an argument for the subject
    • setFrom(): It takes the name and the mailing address
    • setTo(): It takes the name and the mailing address
    • setBody(): It takes the email body and mime type. Here we passed in our template and set the email to text/html
  4. Finally we send the email.

There are more methods in Swift Mailer. Check out the documentation on the Swift Mailer web site (http://swiftmailer.org/).

The partial email template

Lastly, we need to create a partial template that will be used in the email body. In the templates folder of the signup module, create a file called _activationEmail.php and add the following code to it:

Hi <?php echo $name; ?>, <br /><br />
Thank you for signing up to our newsletter.
<br /><br />
Thank you,
<br />
<strong>The Team</strong>

The partial is no different from a regular template. We could have opted to pass on the body as a string, but using the template keeps our code uniform. Our signup process now incorporates the functionality to send an email.

The purpose of this example is to show you how to send an automated email using a third-party library. For a real application, you should most certainly implement a two-phase option wherein the user must verify his or her action.

Flashing temporary values

Sometimes it is necessary to set a temporary variable for one request, or make a variable available to another action after forwarding but before having to delete the variable. Symfony provides this level of functionality within the sfUser object known as a flash variable. Once a flash variable has been set, it lasts until the end of the overall request before it is automatically destroyed.

Setting and getting a flash attribute is managed through two of the sfUser methods. Also, you can test for a flash variable's existence using the third method of the methods listed here:

  • $this->getUser()->setFlash($name, $value, $persist = true)
  • $this->getUser()->getFlash($name)
  • $this->getUser()->hasFlash($name)

Although a flash variable will be available by default when a request is forwarded to another action, setting the argument to false will delete the flash variable before it is forwarded.

To demonstrate how useful flash variables can be, let's readdress the signup form. After a user submits the signup form, the form is redisplayed. I further mentioned that you could create another action to handle a 'thank you' template. However, by using a flash variable we will not have to do so.

As a part of the application logic for the form submission, we can set a flash variable. Then after the action redirects the request, the template can test whether there is a flash variable set. If there is one, the template should show a message rather than the form.

Let's add the $this->getUser()->setFlash() function to the submit action in the apps/frontend/modules/signup/actions/actions.class.php file:

//Include the swift lib
require_once('lib/vendor/swift-mailer/lib/swift_init.php');
//set Flash
$this->getUser()->setFlash('Form', 'completed');
try{

I have added the flash variable just under the require_once() statement. After the user has submitted a valid form, this flash variable will be set with the name of the Form and have a value completed.

Next, we need to address the template logic. The template needs to check whether a flash variable called Form is set. If it is not set, the template shows the form. Otherwise it shows a thank you message. This is implemented using the following code:

<?php if(!$sf_user->hasFlash('Form')): ?>
<form action="<?php echo url_for('@signup_submit') ?>"
method="post" name="Newsletter">
<div style="height: 30px;">
<div style="width: 150px; float: left">
<?php echo $form['first_name']->renderLabel() ?></div>
<?php echo $form['first_name']->render(($form['first_name']->
hasError())? array('class'=>'boxError'): array
('class'=>'box')) ?>
<?php echo ($form['first_name']->hasError())?
' <span class="errorMessage">*
'.$form['first_name']->getError(). '</span>': '' ?>
<div style="clear: both"></div>
</div>
....
</form>
<?php else: ?>
<h1>Thank you</h1>
You are now signed up.
<?php endif ?>

The form is now wrapped inside an if/else block. Accessing the flash variables from a template is done through $sf_user. To test if the variable has been set, I have used the hasFlash() method, $sf_user->hasFlash('Form'). The else part of the statement contains the text rather than the form. Now if you submit your form, you will see the result as shown in the following screenshot:

User Interaction and Email Automation in Symfony 1.3: Part2

We have now implemented an entire module for a user to sign up for our newsletter. Wouldn't it be really good if we could add this module to another application without all the copying, pasting, and fixing?

Symfony 1.3 Web Application Development Design, develop, and deploy feature-rich, high-performance PHP web applications using the Symfony framework
Published: September 2009
eBook Price: £14.99
Book Price: £24.99
See more
Select your format and quantity:

Creating a plugin

Symfony has many wonderful and time-saving features. But the two I am very fond of are the Symfony's plugin architecture and the currently increasing plugin repository.

It is always a good idea to create loosely coupled classes that can be integrated into other applications. In Symfony, not only should you follow this concept when coding, but also think of coding a module that you can use in another application. If you can manage this, then refactoring your module to a plugin will eventually save you many hours.

The signup module that we have created is fairly simple and straight forward, and is something which many sites do use. By refactoring our module and producing a plugin, you can install this plugin on any of your applications without having to rewrite any code.

Before we start, I think it's important that we have a look at the naming conventions. All the plugins available on the Symfony wiki follow the naming convention of sfPluginNamePlugin. Following this structure, I have made it a policy at Agile labs to name our plugins as alPluginNamePlugin. Also, a module-naming convention is important as we do not want to cause any conflicts with the modules already present in an application.

To help us in creating the plugin skeleton directory structure and to package the plugin, we are going to install sfTaskExtraPlugin. Go to the Symfony web site and download the plugin from http://www.symfony-project.org/plugins/sfTaskExtraPlugin to your hard disk. Once you have downloaded it, you can install it. I have downloaded the plugin to the Documents folder on my computer and to install it, I enter the following command:

$/home/timmy/workspace/milkshake>symfony plugin:install /home/timmy/
Documents/sfTaskExtraPlugin-0.0.1.tgz
$/home/timmy/workspace/milkshake>symfony cc

Remember that now we must enable this plugin before we can use it. Open up the project application file config/ProjectConfiguration.clsss.php and add the sfTaskExtraPlugin to the array using the following command:

$this->enablePlugins(array('sfPropelPlugin','DbFinderPlugin',
'sfTaskExtraPlugin'));

We can now start creating the directory structure for our plugin. Create a folder called alSignupPlugin in the plugins/ folder. Just like an application, we can have modules within the plugin too. Hence, create a modules/ folder inside the alSignupPlugin folder. Next, we can move our signup module folder apps/frontend/modules/signup that we created earlier to the new module folder that we just created. Once you have moved it, rename the folder from signup to alSignup.

Be careful when copying entire folders to other locations when they are in subversion. Every folder will contain a hidden .svn folder. This will cause headaches later when you want to commit the code. To copy them, use the svn export command.

While we are creating folders, you can go ahead and create the config/, data/ data/fixtures, lib/, lib/forms/, and lib/model folders in the plugins/alSignupPlugin directory. Your directory structure should reflect the following:

plugins/alSignupPlugin/config
/data
/data/fixtures
/lib
/lib/form
/lib/model
/test

There are nine steps in refactoring our module to a plugin. These are the steps:

  1. A plugin can have its own schema, which is built after the main schema for the application. So we need to create a schema.xml file in the plugins/orSignupPlugin/config directory. We are then going to copy the schema code for the newsletter_signups and newsletter_adverts tables from the main schema.xml file, and place them in the plugin's schema.xml file. Because we have moved this, we delete this out of the main schema file. The following is how the plugin's schema.xml will look:
    <?xml version="1.0" encoding="UTF-8"?>
    <database name="propel" defaultIdMethod="native" noXsd="true"
    package="plugins.alSignupPlugin.lib.model">
    <table name="alsignup_newsletter_adverts" idMethod="native"
    phpName="AlSignupNewsletterAds">
    <column name="newsletter_adverts_id" type="INTEGER"
    required="true" autoIncrement="true"
    primaryKey="true" />
    <column name="advertised" type="VARCHAR" size="30"
    required="true" />
    </table>
    <table name="alsignup_newsletter_signups" idMethod="native"
    phpName="AlSignupNewsletterSignup">
    <column name="id" type="INTEGER" required="true"
    autoIncrement="true" primaryKey="true" />
    <column name="first_name" type="VARCHAR" size="20"
    required="true" />
    <column name="surname" type="VARCHAR" size="20"
    required="true" />
    <column name="email" type="VARCHAR" size="100"
    required="true" />
    <column name="activation_key" type="VARCHAR" size="100"
    required="true" />
    <column name="activated" type="BOOLEAN" default="0"
    required="true" />
    <column name="newsletter_adverts_id" type="INTEGER"
    required="true"/>
    <foreign-key foreignTable="alsignup_newsletter_adverts"
    onDelete="CASCADE">
    <reference local="newsletter_adverts_id"
    foreign="newsletter_adverts_id" />
    </foreign-key>
    <column name="created_at" type="TIMESTAMP" required="true" />
    <column name="updated_at" type="TIMESTAMP" required="true" />
    </table>
    </database>

    We have to make a few edits to the copied code in order to keep the naming convention in line with our plugin.

    • The package attribute in the <database> tag needs to be changed from lib.mobel to plugins.orSignupPlugin.lib.model. When we build our models and forms, we want them to be placed in our plugin's lib/ folder rather than that of the project. If you do not change this, when you build the models, they will be placed in the main lib/model. I have also changed the table names and the phpName (the models' name) to reflect the name of the plugin. This will prevent any naming conflicts. I have prefixed alsignup_ to the table names and AlSignup to the phpName.
    • I have had to change the foreign_table value to alsignup_newsletter_adverts to reflect the new table name.

    Once this is in place, remove both the newsletter_signups and the newsletter_ tables from the main config/schema.xml file.

  2. Since we have just moved over the schema, let's also move over the fixtures. You will recall that we have data to populate the newsletter_adverts table, which is now called alsignup_newsletter_adverts. The fixtures file for a plugin works exactly the same as it does for the applications. We must create a fixtures.yml file in the plugins/alSignupPlugin/data/fixtures/ folder. Once created, insert the following into it:
    AlSignupNewsletterAds:
    nsa1:
    advertised: Internet Search
    nsa2:
    advertised: High Street
    nsa3:
    advertised: Poster

    The model name has now changed from NewsletterAds to AlSignupNewsletterAds to reflect the new model when we build it. Don't forget to remove the old fixtures from the fixtures file.

  3. Each plugin needs to have its own config.php file. This file is where we specify all the configuration settings for the plugin. As a plugin should be more or less plug and play, we have to add the plugin's routing rules to the start of the application's routing rules. In the plugins/alSignup/config/config.php file we configure our plugin to prepend the routing rules (which we will place in another file) only if the plugin is enabled as shown here:
    if (sfConfig::get('app_alSignup_routes_register', true) &&
    in_array('alSignup', sfConfig::get('sf_enabled_modules')))
    {
    $this->dispatcher->connect('routing.load_configuration',
    array('alSignupRouting',
    'listenToRoutingLoadConfigurationEvent'));
    }

    We cannot just create a routing.yml file in the plugin because of the order of the routing rules when loading. Therefore, we need to register an event listener, routing.load_configuration, and prepend our rules to it. The second argument passed in to the connect() method is an array. The first argument of this array is the name of the class that handles the routing rules and the second argument is the name of the function that holds them, listenToRoutingLoadConfigurationEvent. Create a file called alSignupRouting.php and store it in the plugin/alSignup/lib folder. This file will hold our routing rules. Open the file and insert the following class:

    class alSignupRouting
    {
    /**
    * Listens to the routing.load_configuration event.
    *
    * @param sfEvent An sfEvent instance
    */
    static public function
    listenToRoutingLoadConfigurationEvent(sfEvent $event)
    {
    $r = $event->getSubject();
    // preprend our routes
    $r->prependRoute('signup', new sfRoute('/signup',
    array('module' => 'alSignup', 'action' => 'index')));
    $r->prependRoute('signup_submit', newsfRoute('/signup/submit',
    array('module' => 'alSignup', 'action' => 'submit')));
    }
    }

    As you can see, we have prepended each route to the application's routing rules. The first argument is the label, followed by a sfRoute object that is initialized with the URL, module, and action. Now that we have added the routing rules to our plugin, we can remove them from the apps/frontend/config/routing.yml file.

    We can now go ahead and rebuild the models and forms by using the following pake task:

    $/home/timmy/workspace/milkshake>symfony propel:build-all-load
    --no-confirmation
    $/home/timmy/workspace/milkshake>symfony cc

    This will build our models and forms in the plugin's lib/ folder and will insert the fixtures data back into the database for us.

  4. After running the above tasks, you will notice that the new models and forms have been created for us. These new models reflect the change of table names in the schema. As we are now using models with a different name, we have to change the references to them in the action as well as in the form. Before we do this, we need to re-add a function to the newly created AlSignupNewsletterAds model. We created a __toString() function in the NewsletterAds model, which now needs to be transferred over to AlSignupNewsletterSignup. Open the plugins/AlSignupPlugin/lib/model/AlSignupNewsletterSignup.php file and add the following function:
    public function __toString()
    {
    return $this->getAdvertised();
    }

    As we do not need the Newsletter and the NewsletterAds models anymore, you can go ahead and delete them from the lib/model, lib/model/om, and lib/model/map folders as shown here:

    rm -f lib/model/Newsl*
    rm -f lib/om/BaseNewsl*
    rm -f lib/map/NewsletterBuilder.php
  5. With the models migrated over, we need to do the same with the form. If you look in the plugin/alSignupPlugin/lib/form directory, you will see that both the forms, AlSignupNewsletterSignupForm and AlSignupNewsletterSignupForm, have been created. First, you can delete AlSignupNewsletterSignupAdsForm and BaseAlSignupNewsletterSignupAdsForm as we do not need a form for this table. Next, open up the form that we initially created, lib/form/NewsletterSignupForm.php, and copy the code within the configure() method over to plugin/alSignupPlugin/lib/form/AlSignupNewlsetterSignupForm. Once that is done, you need make two changes. In both the widget and validator initialization, there is a reference to NewsletterAds, 'newsletter_adverts_id' => new sfWidgetFormPropelChoice(array('model' => 'NewsletterAds'. Here, change NewsletterAds to AlSignupNewsletterAds as this is now the name of the new model.
  6. The final part of the migration is to amend the calls to the old NewsletterSignupForm within the action and rename the action class. Also, we have to change the configuration references to the module.yml file values that we had set for our email. Open the action.class.php file for the plugin located in the plugins/alSignupPlugin/modules/actions folder. First, change the action's class name from SignupActions to alSignupActions. At the moment, the action is instantiating the old form (which should have been deleted). Therefore, change both occurrences of

    $this->form = new NewsletterSignupForm();

    to:

    $this->form = new AlSignupNewsletterSignupForm();

    Next, we must amend the variables that we use to retrieve the values from the module.yml file that we had created earlier to hold the email values. I have amended them in the following code to reference the alsignup module, and not signup:

    try{
    //Sendmail
    $transport = Swift_SendmailTransport::newInstance();
    $mailBody = $this->getPartial('activationEmail',
    array('name' => $this->form->getValue('first_name')));
    $mailer = Swift_Mailer::newInstance($transport);
    $message = Swift_Message::newInstance();
    $message->setSubject(sfConfig::get
    ('mod_alsignup_mailer_subject'));
    $message->setFrom(array(sfConfig::get
    ('mod_alsignup_mailer_from_email') => sfConfig::get
    ('mod_alsignup_mailer_from_name')));
    $message->setTo(array($this->form->getValue('email') =>
    $this->form->getValue('first_name')));
    $message->setBody($mailBody, 'text/html');
    if(sfConfig::get('mod_alsignup_mailer_deliver'))
    {
    $result = $mailer->send($message);
    }
    }

    We have now migrated the signup plugin to a reuseable plugin.

  7. Next we perform housekeeping by deleting unneeded files and removing the old tables in the database, that is, all forms and filters.
  8. We have now created our first plugin. But in order to use it, we must enable the plugin's module for the frontend application. We enable this in the apps/frontend/config/settings.yml file. In the all key, add the enable_modules parameter just as I have done here:
    all:
    .settings:
    # Form security secret (CSRF protection)
    csrf_secret: a15ba0eef94f781b04886f7453fe506d57feb431
    # Unique secret to enable CSRF protection or false to disable
    # Output escaping settings
    escaping_strategy: true
    # Determines how variables are made available to templates. Accepted
    values: on, off.
    escaping_method: ESC_SPECIALCHARS # Function or helper
    used for escaping. Accepted values: ESC_RAW, ESC_ENTITIES, ESC_JS,
    ESC_JS_NO_ENTITIES, and ESC_SPECIALCHARS.
    enabled_modules: [default,alSignup]
  9. Once that is all done, do not forget to clear the cache using the following command:
    >symfony cc

If you have followed all of the aforementioned steps, then the plugin should now be active and working.

Packaging a plugin

There are three ways to include a plugin into our application:

  • Copying a plugin into the plugin folder
  • Using SVN externals to reference the plugin
  • Using the Symfony plugin:install task to download and install the plugin

SVN externals are great and recommended, but sometimes it is easier to distribute your plugin as a package. This is very simple as the sfTaskExtraPlugin plugin that we had installed earlier comes with a package task.

First, add any additional information to the LICENCE and README files in the alSignupPlugin root directory. You can also modify the package.xml.templ file as this is the default template used to generate the package.xml file. When you are happy, run the following command:

>symfony plugin:package --plugin-version="0.1.0" alSignupPlugin

You will now be asked a few further questions, so fill them in. The output of this is shown in the following screenshot:

User Interaction and Email Automation in Symfony 1.3: Part2

Summary

Symfony's form framework is another fantastic feature that helps developers with the tedious task of writing forms. Whether it is a Propel-based form or a simple form, creating forms and validating them is simple and rapid, not to mention more object orientated too. We created our own formatting class that produced a nice-looking prototype, and then progressed to create a fully-customized form.

We have seen how useful plugins are, but there is not a plugin for everything. Here we introduced you to Swift Mailer for sending out emails and integrated it into our application with a minimum effort, again showing you how Symfony can be expanded to use the other third-party libraries.

We then converted our signup module into a fully-working plugin. This demonstrated that when you build other web applications, you should write more generic and loosely coupled code so that it can be packaged up and reused in other projects.

 

If you have read this article you may be interested to view :

 

Symfony 1.3 Web Application Development Design, develop, and deploy feature-rich, high-performance PHP web applications using the Symfony framework
Published: September 2009
eBook Price: £14.99
Book Price: £24.99
See more
Select your format and quantity:

About the Author :


Tim Bowler

Tim Bowler has a Bachelor's Degree in Computer Science, a Masters Degree in Internet Technologies and E-Commerce, and is currently studying for his PhD part time. He has over 10 years of experience in web application development and project management. His experience and determination has gained him membership in the Institute of Engineering and Technology and he is a Chartered IT Professional. Tim started his career developing web applications in PHP4 for a digital media agency in London. Later he introduced agile and scrum into the development process along with Symfony. Tim is now the Managing Director of Agile Labs which is a web application development and agile coaching company.

Books From Packt

jQuery 1.3 with PHP
jQuery 1.3 with PHP

Ext JS 3.0 Cookbook
Ext JS 3.0 Cookbook

PHP Team Development
PHP Team Development

Matplotlib for Python Developers
Matplotlib for Python Developers

Apache MyFaces Trinidad 1.2: A Practical Guide
Apache MyFaces Trinidad 1.2: A Practical Guide

3D Game Development with Microsoft Silverlight 3: Beginner's Guide
3D Game Development with Microsoft Silverlight 3: Beginner's Guide

Zend Framework 1.8 Web Application Development
Zend Framework 1.8 Web Application Development

Joomla! 1.5x Customization: Make Your Site Adapt to Your Needs
Joomla! 1.5x Customization: Make Your Site Adapt to Your Needs

 

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software