|
|
Want to know more about Packt's Article Network? Interested in contributing your article ideas? Please visit our FAQ for more information. See More BROWSE
All Titles WordPress Web Services SOA BPEL Web Graphics & Video Web Development RAW Portugues, Espanol, Italiano, French PHP/MySQL Oracle Open Source Networking & Telephony Moodle Microsoft & .NET Linux Servers jQuery Joomla! JBoss Java e-Learning e-Commerce Dynamics Drupal CRM Cookbook Content Management Beginner Guides Architecture and Analysis AJAX Future Titles Recently Published Titles Developing an application in Symfony is easy and time-saving, and one of the best ways to demonstrate that is to create a web site. By the end of this article by Tim Bowler, we will have an initial prototype, which will serve as a starting point. Along the way you will be introduced to the MVC flow within Symfony where you will understand about the business and application logic, and designing the database. In this article you will learn how to:
See More |
User Interaction and Email Automation in Symfony 1.3: Part 1
The signup moduleWe want to provide the users with the functionality to enter their name, email address, and how they found our web site. We want all this stored in a database and to have an email automatically sent out to the users thanking them for signing up. To start things off, we must first add some new tables to our existing database schema. The structure of our newsletter table will be straightforward. We will need one table to capture the users' information and a related table that will hold the names of all the places where we advertised our site. I have constructed the following entity relationship diagram to show you a visual relationship of the tables:
All the code used in this article can be accessed here. Let's translate this diagram into XML and place it in the config/schema.xml file: <table name="newsletter_adverts" idMethod="native" phpName="NewsletterAds"> We will need to populate the newsletter_adverts table with some test data as well. Therefore, I have also appended the following data to the fixtures.yml file located in the data/fixtures/ directory: NewsletterAds: With the database schema and the test data ready to be inserted into the database, we can once again use the Symfony tasks. As we have added two new tables to the schema, we will have to rebuild everything to generate the models using the following command: $/home/timmy/workspace/milkshake>symfony propel:build-all-load --no-confirmation Now we have populated the tables in the database, and the models and forms have been generated for use too. Binding a form to a database tableSymfony contains a whole framework just for the development of forms. The forms framework makes building forms easier by applying object-oriented methods to their development. Each form class is based on its related table in the database. This includes the fields, the validators, and the way in which the forms and fields are rendered. A look at the generated base classRather than starting off with a simple form, we are going to look at the base form class that has already been generated for us as a part of the build task we executed earlier. Because the code is generated, it will be easier for you to see the initial flow of a form. So let's open the base class for the NewsletterSignupForm form. The file is located at lib/form/base/BaseNewsletterSignupForm.class.php: class BaseNewsletterSignupForm extends BaseFormPropel There are five areas in this base class that are worth noting:
The base class BaseNewsletterSignupForm contains all the components needed to generate the form for us. So let's get the form on a page and take a look at the method to customize it. There are many widgets that Symfony provides for us. You can find the classes for them inside the widget/ directory of your Symfony installation. The Symfony propel task always generates a form class and its corresponding base class. Of course, not all of our tables will need to have a form bound to them. Therefore, delete all the form classes that are not needed. Rendering the formRendering this basic form requires us to instantiate the form object in the action. Assigning the form object to the global $this variable means that we can pass the form object to the template just like any other variable. So let's start by implementing the newsletter signup module. In your terminal window, execute the generate:module task like this: $/home/timmy/workspace/milkshake>symfony generate:module frontend signup Now we can start with the application logic. Open the action class from apps/frontend/modules/signup/actions/actions.class.php for the signup module and add the following logic inside the index action: public function executeIndex(sfWebRequest $request) As I had mentioned earlier, the form class deals with the form validation and rendering. For the time being, we are going to stick to the default layout by allowing the form object to render itself. Using this method initially will allow us to create rapid prototypes. Let's open the apps/frontend/signup/templates/indexSuccess.php template and add the following view logic: <form action="<?php echo url_for('signup/submit') ?>" method="POST">The form class is responsible for rendering of the form elements only. Therefore, we have to include the <form> and submit HTML tags that wrap around the form. Also, the default format of the form is set to 'table'. Again, we must also add the start and end tags of the <table>. At this stage, we would normally be able to view the form in the browser. But doing so will raise a Symfony exception error. The cause of this is that the results retrieved from the newsletter_adverts table are in the form of an array of objects. These results need to populate the select box widget. But in the current format, this is not possible. Therefore, we have to convert each object into its string equivalent. To do this, we need to create a PHP magic function of __toString() in the DAO class NewsletterAds. The DAO class for NewlsetterAds is located at lib/model/NewsletterAds.php just as all of the other models. Here we need to represent each object as its name, which is the value in the advertised column. Remember that we need to add this method to the DAO class as this represents a row within the results, unlike the peer class that represents the entire result set. Let's add the function to the NewsletterAds class as I have done here: class NewsletterAds extends BaseNewsletterAds We are now ready to view the completed form. In your web browser, enter the URL http://milkshake/frontend_dev.php/signup and you will see the result shown in the following screenshot:
As you can see, although the form has been rendered according to our table structure, the fields which we do not want the user to fill in are also included. Of course, we can change this quiet easily. But before we take a look at the layout of the form, let's customize the widgets and widget validators. Now we can begin working on the application logic for submitting the form. Customizing form widgets and validatorsAll of the generated form classes are located in the lib/form and the lib/form/base directories. The latter is where the default generated classes are located, and the former is where the customizable classes are located. This follows the same structure as the models. Each custom form class inherits from its parent. Therefore, we have to override some of the functions to customize the form. Let's customize the widgets and validators for the NewsletterSignupForm. Open the lib/forms/NewsletterSignupForm.class.php file and paste the following code inside the configure() method: //Removed unneeded widgets Let's take a closer look at the code. Removing unneeded fieldsTo remove the fields that we do not want to be rendered, we must call the PHP unset() method and pass in the fields to unset. As mentioned earlier, all of the fields that are rendered need a corresponding validator, unless we unset them. Here we do not want the created_at and activation_key fields to be entered by the user. To do so, the unset() method should contain the following code: unset( Modifying the form widgetsAlthough it'll be fine to use the remaining widgets as they are, let's have a look at how we can modify them: //Modify widgets There are several types of widgets available, but our form requires only two of them. Here we have used the sfWidgetFormInput() and sfWidgetFormPropelChoice() widgets. Each of these can be initialized with several values. We have initialized the email and newsletter_adverts_id widgets with a label. This basically renders the label field associated to the widget on the form. We do not have to include a label because Symfony adds the label according to the column name. Adding form validatorsLet's add the validators in a similar way as we have added the widgets: //Add validation Our form will need four different types of validators:
As mentioned earlier, all the fields must have a validator. Although it's not recommended, you can allow extra parameters to be passed in. To achieve this, there are two steps:
Form naming convention and setting its styleThe final part we added is the naming convention for the HTML attributes and the style in which we want the form rendered. The HTML output will use our naming convention. For example, in the following code, we have set the convention to newsletter_signup[fieldname] for each input field's name. //Set form name Two formats are shipped with Symfony that we can use to render our form. We can either render it in an HTML table or an unordered list. As we have seen, the default is an HTML table. But by setting this as list, the form is now rendered as an unordered HTML list, just like the following screenshot. (Of course, I had to replace the <table> tags with the <ul> tags.)
Symfony 1.3 Web Application Development
Submitting the formNow that we have the form rendered on our template, we need to handle the application logic for submitting the form. The first step is to add the routing rules for our form page and its submission. The form sits within the signup module, and therefore, we will keep all of the functionality there too. Open the routing file apps/frontend/config/routing.yml, and add the following routes: signup: Here we have created two rules, signup and signup_submit. The first rule will be routed to the index action and the second will be routed to the submit action in our signup module. Next, we will create the submit action. Open up the actions class from apps/frontend/modules/signup/actions/actions.class.php and create the submit action: public function executeSubmit(sfWebRequest $request) Looking over the application logic of our submit action, we first need to instantiate NewsletterSignupForm, after which we will do two things. First, we will test the request method as the form should be a post. This is handled by the $request->isMethod('post') method. Secondly, we need to bind the submitted and cleaned form values to the form that we just instantiated. There are two ways to do this:
If the request is a post and the form is valid, the redirect() method is called. (Notice how we can also add the routing label to this method.) We use this method because it forces a redirect, which means if the user clicks on the refresh button, the form will not be re-submitted. If you wanted to display a thank you page, you can, of course, create another action and a template to handle this. But we will address this later in the article. You should now be able to go ahead and test the form. In the following screenshot, I have tried to submit the form without a Surname and an incomplete Email Address, which resulted in the anticipated errors:
Changing the global rendering of formsSo far, we have seen how forms are rendered globally, either as a table or an unordered list. Of course, the forms framework makes the global rendering of forms very easy to change and extend. To demonstrate this, we are going to create our own format. This new format will render the fields within the <div> tags. To create our own form format, we need to create a new class that extends the sfWidgetFormSchemaFormatter class. The naming convention for the actual class that we will create needs to follow sfWidgetFormSchemaFormatterName. Therefore, our class will be called sfWidgetFormSchemaFormatterDiv.class.php. Because this class will need to be accessed globally, it must be placed in either the lib/ folder or in a subfolder within the lib/ directory. Create a new folder in the lib/ directory called widget, so that the path reflects lib/widget. In this folder, we are going to create our formatter class. Create a file named WidgetFormSchemaFormatterDiv.class.php in the newly created widget/ directory. Open the new file and create the following class: <?php Each variable holds a part of the form. I have added the row formatting to the $rowFormat variable. The other variables hold the error messages for the row and the container. With our new formatter now available, we just have to update the NewsletterSignupForm.class.php file so that it is able to use it. Change the following line $this->widgetSchema->setFormFormatterName('list');to $this->widgetSchema->setFormFormatterName('div');The final part is to style the error message. In the formatter, I have used a reference to a CSS class called errorMessage. Therefore, in the web/css/main.css file in our stylesheet, I have added the following code: .errorMessage{ color: red; font-weight: bold}After refreshing your browser on the signup page (http://milkshake/frontend_dev.php/signup), you will see the new layout along with the validation errors as shown in the following screenshot:
Customizing the rendering of the formTill now the rendering of forms has all been handled by the default rendering method. From a prototype point of view, this method has allowed us to create a nice, clean form. The only problem we now face is that there are situations where this rendering is not flexible enough. To render the form, we have been echoing the $form object in the template. This, in fact, is a shortcut to $form->render(). However, there are several rendering functions available for use. We will be using these to customize our form on the template further. To demonstrate this, I will apply these functions to the email field before presenting to you the final version. The email section of the form can be broken up into three areas: the label, the form field, and the error message. We are going to use three of the rendering functions to completely customize the email section:
With this in mind for the moment, I am now going to show you how we will render the email row on our form: <div style="height: 30px;"> As you can see, we now have a complete control over how a form is rendered. To complete rendering, I have applied this technique to the other form fields too with the following code: <form action="<?php echo url_for('@signup_submit') ?>" method="post"You must have noted that I have added a reference to another CSS class, boxError. The following code too has been added to the main.css stylesheet located in the web/css/ directory: .boxError{ border: 2px solid red;}Form security for the userWhen we created our project, there were many default settings configured for us. One such setting concerns the forms and Cross Site Request Forgery (CSRF). To help prevent this, all forms must contain a hidden field called csr_token. As you can see in our previous form, I have included this hidden field just above the submit button. This value is derived from csrf_secret, located in the application's settings file at apps/frontend/config/settings.yml. Although it is randomly generated, it is advised that you should change this. After you have completed the template by adding the above code and the CSS, check out the form at http://milkshake/frontend_dev.php/signup in your browser. In the following screenshot, I have again tried to submit the form containing errors:
As you can see, the error messages appear with a red border around the input fields. To finish off the signup section, we will need to create a link to the signuppage. I have placed my navigation link on the lefthand side of the page alongwith all the other links. To do this, open up the application layout template from apps/frontend/templates/layout.php and add the following underneath the other navigation links: <li style="margin:0;padding:0.25em 0.5em 0.25em 0.5em; width: 150px; Notice how we can still use the routing tag name @signup to reference the route. Creating a simple formWe have seen how powerful forms in Symfony are, especially when bounded to a database table. Not all forms, though, will need to reference a database table. Instead, we can create a simple form for which the process is practically the same. The only difference is that we have to manually create the form class. Just to give you an example, let's say we want to create a simple feedback form that contains three fields called name, email, and message. You would create the new form class at the same place where all of the other form classes are stored—in lib/form/—and perhaps, name the file as FeedbackForm.class.php. With this created, let's create the class as follows: class FeedbackForm extends BaseForm Comparing this to our Propel, the only difference is that the parent class is BaseForm, as the same logic applies to the forms. If you look at the Propel forms class hierarchy too, both eventually extend sfForm, as sfForm is the parent class for all forms. The form submission process is also handled in exactly the same way as Propel. However, you only need to call the bind() method, as there is no database table to save the form object. The following code shows the form submission process: $this->form = new FeedbackForm(); There are two possible ways to retrieve the values from a simple form:
Although I have presented you with two ways to retrieve the form values, it is extremely important that you access the form values through the form object as shown in the latter of the two ways. This is because the values there have been cleaned. >> Continue Reading: User Interaction and Email Automation in Symfony 1.3: Part2 If you have read this article you may be interested to view :
Symfony 1.3 Web Application Development
About the AuthorTim 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
|
|
| ||||||||