Development

/doc/tags/RELEASE_1_1_0_BETA4/book/10-Forms.txt

You must first sign up to be able to contribute.

root/doc/tags/RELEASE_1_1_0_BETA4/book/10-Forms.txt

Revision 8289, 54.2 kB (checked in by francois, 9 months ago)

[doc 1.1] Added warning about the need for sfCompat10Plugin when required

Line 
1 Chapter 10 - Forms
2 ==================
3
4 When writing templates, much of a developer's time is devoted to forms. Despite this, forms are generally poorly designed. Since much attention is required to deal with default values, formatting, validation, repopulation, and form handling in general, some developers tend to skim over some important details in the process. Accordingly, symfony devotes special attention to this topic. This chapter describes the tools that automate many of these requirements while speeding up forms development:
5
6   * The form helpers provide a faster way to write form inputs in templates, especially for complex elements such as dates, drop-down lists, and rich text.
7   * When a form is devoted to editing the properties of an object, the templating can be further accelerated by using object form helpers.
8   * The YAML validation files facilitate form validation and repopulation.
9   * Validators package the code required to validate input. Symfony bundles validators for the most common needs, and it is very easy to add custom validators.
10
11 Form Helpers
12 ------------
13
14 In templates, HTML tags of form elements are very often mixed with PHP code. Form helpers in symfony aim to simplify this task and to avoid opening `<?php echo` tags repeatedly in the middle of `<input>` tags.
15
16 ### Main Form Tag
17
18 As explained in the previous chapter, you must use the `form_tag()` helper to create a form, since it transforms the action given as a parameter into a routed URL. The second argument can support additional options--for instance, to change the default `method`, change the default `enctype`, or specify other attributes. Listing 10-1 shows examples.
19
20 Listing 10-1 - The `form_tag()` Helper
21
22     [php]
23     <?php echo form_tag('test/save') ?>
24      => <form method="post" action="/path/to/save">
25
26     <?php echo form_tag('test/save', 'method=get multipart=true class=simpleForm') ?>
27      => <form method="get" enctype="multipart/form-data" class="simpleForm"action="/path/to/save">
28
29 As there is no need for a closing form helper, you should use the HTML `</form>` tag, even if it doesn't look good in your source code.
30
31 ### Standard Form Elements
32
33 With form helpers, each element in a form is given an id attribute deduced from its name attribute by default. This is not the only useful convention. See Listing 10-2 for a full list of standard form helpers and their options.
34
35 Listing 10-2 - Standard Form Helpers Syntax
36
37     [php]
38     // Text field (input)
39     <?php echo input_tag('name', 'default value') ?>
40      => <input type="text" name="name" id="name" value="default value" />
41
42     // All form helpers accept an additional options parameter
43     // It allows you to add custom attributes to the generated tag
44     <?php echo input_tag('name', 'default value', 'maxlength=20') ?>
45      => <input type="text" name="name" id="name" value="default value"maxlength="20" />
46
47     // Long text field (text area)
48     <?php echo textarea_tag('name', 'default content', 'size=10x20') ?>
49      => <textarea name="name" id="name" cols="10" rows="20">
50           default content
51         </textarea>
52
53     // Check box
54     <?php echo checkbox_tag('single', 1, true) ?>
55     <?php echo checkbox_tag('driverslicense', 'B', false) ?>
56      => <input type="checkbox" name="single" id="single" value="1"checked="checked" />
57         <input type="checkbox" name="driverslicense" id="driverslicense"value="B" />
58
59     // Radio button
60     <?php echo radiobutton_tag('status[]', 'value1', true) ?>
61     <?php echo radiobutton_tag('status[]', 'value2', false) ?>
62      => <input type="radio" name="status[]" id="status_value1" value="value1"checked="checked" />
63         <input type="radio" name="status[]" id="status_value2" value="value2" />
64
65     // Dropdown list (select)
66     <?php echo select_tag('payment',
67       '<option selected="selected">Visa</option>
68        <option>Eurocard</option>
69        <option>Mastercard</option>')
70     ?>
71      => <select name="payment" id="payment">
72           <option selected="selected">Visa</option>
73           <option>Eurocard</option>
74           <option>Mastercard</option>
75         </select>
76
77     // List of options for a select tag
78     <?php echo options_for_select(array('Visa', 'Eurocard', 'Mastercard'), 0) ?>
79      => <option value="0" selected="selected">Visa</option>
80         <option value="1">Eurocard</option>
81         <option value="2">Mastercard</option>
82
83     // Dropdown helper combined with a list of options
84     <?php echo select_tag('payment', options_for_select(array(
85       'Visa',
86       'Eurocard',
87       'Mastercard'
88     ), 0)) ?>
89      => <select name="payment" id="payment">
90           <option value="0" selected="selected">Visa</option>
91           <option value="1">Eurocard</option>
92           <option value="2">Mastercard</option>
93         </select>
94
95     // To specify option names, use an associative array
96     <?php echo select_tag('name', options_for_select(array(
97       'Steve'  => 'Steve',
98       'Bob'    => 'Bob',
99       'Albert' => 'Albert',
100       'Ian'    => 'Ian',
101       'Buck'   => 'Buck'
102     ), 'Ian')) ?>
103      => <select name="name" id="name">
104           <option value="Steve">Steve</option>
105           <option value="Bob">Bob</option>
106           <option value="Albert">Albert</option>
107           <option value="Ian" selected="selected">Ian</option>
108           <option value="Buck">Buck</option>
109         </select>
110
111     // Dropdown list with multiple selection (selected values can be an array)
112     <?php echo select_tag('payment', options_for_select(
113       array('Visa' => 'Visa', 'Eurocard' => 'Eurocard', 'Mastercard' => 'Mastercard'),
114       array('Visa', 'Mastercard'),
115     ), array('multiple' => true))) ?>
116
117      => <select name="payment[]" id="payment" multiple="multiple">
118           <option value="Visa" selected="selected">Visa</option>
119           <option value="Eurocard">Eurocard</option>
120           <option value="Mastercard">Mastercard</option>
121         </select>
122     // Drop-down list with multiple selection (selected values can be an array)
123     <?php echo select_tag('payment', options_for_select(
124       array('Visa' => 'Visa', 'Eurocard' => 'Eurocard', 'Mastercard' => 'Mastercard'),
125       array('Visa', 'Mastercard')
126     ), 'multiple=multiple') ?>
127      => <select name="payment" id="payment" multiple="multiple">
128           <option value="Visa" selected="selected">
129           <option value="Eurocard">Eurocard</option>
130           <option value="Mastercard" selected="selected">Mastercard</option>
131         </select>
132
133     // Upload file field
134     <?php echo input_file_tag('name') ?>
135      => <input type="file" name="name" id="name" value="" />
136
137     // Password field
138     <?php echo input_password_tag('name', 'value') ?>
139      => <input type="password" name="name" id="name" value="value" />
140
141     // Hidden field
142     <?php echo input_hidden_tag('name', 'value') ?>
143      => <input type="hidden" name="name" id="name" value="value" />
144
145     // Submit button (as text)
146     <?php echo submit_tag('Save') ?>
147      => <input type="submit" name="submit" value="Save" />
148
149     // Submit button (as image)
150     <?php echo submit_image_tag('submit_img') ?>
151      => <input type="image" name="submit" src="/images/submit_img.png" />
152
153 The `submit_image_tag()` helper uses the same syntax and has the same advantages as the `image_tag()`.
154
155 >**NOTE**
156 >For radio buttons, the `id` attribute is not set by default to the value of the `name` attribute, but to a combination of the name and the value. That's because you need to have several radio button tags with the same name to obtain the automated "deselecting the previous one when selecting another" feature, and the `id=name` convention would imply having several HTML tags with the same `id` attribute in your page, which is strictly forbidden.
157
158 -
159
160 >**SIDEBAR**
161 >Handling form submission
162 >
163 >How do you retrieve the data submitted by users through forms? It is available in the request parameters, so the action only needs to call `$this->getRequestParameter($elementName)` to get the value.
164 >
165 >A good practice is to use the same action to display and handle the form. According to the request method (GET or POST), either the form template is called or the form is handled and the request is redirected to another action.
166 >
167 >     [php]
168 >     // In mymodule/actions/actions.class.php
169 >     public function executeEditAuthor()
170 >     {
171 >       if (!$this->getRequest()->isMethod('post'))
172 >       {
173 >         // Display the form
174 >         return sfView::SUCCESS;
175 >       }
176 >       else
177 >       {
178 >         // Handle the form submission
179 >         $name = $this->getRequestParameter('name');
180 >         ...
181 >         $this->redirect('mymodule/anotheraction');
182 >       }
183 >     }
184 >
185 >For this to work, the form target must be the same action as the one displaying it.
186 >
187 >     [php]
188 >     // In mymodule/templates/editAuthorSuccess.php
189 >     <?php echo form_tag('mymodule/editAuthor') ?>
190 >
191 >     ...
192
193 Symfony offers specialized form helpers to do asynchronous requests in the background. The next chapter, which focuses on Ajax, provides more details.
194
195 ### Date Input Widgets
196
197 Forms are often used to retrieve dates. Dates in the wrong format are the main reason for form-submission failures. The `input_date_tag()` helper can assist the user in entering a date with an interactive JavaScript calendar, if you set the `rich` option to `true`, as shown in Figure 10-1.
198
199 Figure 10-1 - Rich date input tag
200
201 ![Rich date input tag](/images/book/F1001.png "Rich date input tag")
202
203 If the `rich` option is omitted, the helper echoes three `<select>` tags populated with a range of months, days, and years. You can display these drop-downs separately by calling their helpers (`select_day_tag()`, `select_month_tag()`, and `select_year_tag()`). The default values of these elements are the current day, month, and year. Listing 10-3 shows the input date helpers.
204
205 Listing 10-3 - Input Date Helpers
206
207     [php]
208     <?php echo input_date_tag('dateofbirth', '2005-05-03', 'rich=true') ?>
209      => a text input tag together with a calendar widget
210
211     // The following helpers require the DateForm helper group
212     <?php use_helper('DateForm') ?>
213
214     <?php echo select_day_tag('day', 1, 'include_custom=Choose a day') ?>
215     => <select name="day" id="day">
216           <option value="">Choose a day</option>
217           <option value="1" selected="selected">01</option>
218           <option value="2">02</option>
219           ...
220           <option value="31">31</option>
221         </select>
222
223     <?php echo select_month_tag('month', 1, 'include_custom=Choose a month use_short_month=true') ?>
224     => <select name="month" id="month">
225           <option value="">Choose a month</option>
226           <option value="1" selected="selected">Jan</option>
227           <option value="2">Feb</option>
228           ...
229           <option value="12">Dec</option>
230         </select>
231
232     <?php echo select_year_tag('year', 2007, 'include_custom=Choose a year year_end=2010') ?>
233      => <select name="year" id="year">
234           <option value="">Choose a year</option>
235           <option value="2006">2006</option>
236           <option value="2007" selected="selected">2007</option>
237           ...
238         </select>
239
240 The accepted date values for the `input_date_tag()` helper are the ones recognized by the `strtotime()` PHP function. Listing 10-4 shows which formats can be used, and Listing 10-5 shows the ones that must be avoided.
241
242 Listing 10-4 - Accepted Date Formats in Date Helpers
243
244     [php]
245     // Work fine
246     <?php echo input_date_tag('test', '2006-04-01', 'rich=true') ?>
247     <?php echo input_date_tag('test', 1143884373, 'rich=true') ?>
248     <?php echo input_date_tag('test', 'now', 'rich=true') ?>
249     <?php echo input_date_tag('test', '23 October 2005', 'rich=true') ?>
250     <?php echo input_date_tag('test', 'next tuesday', 'rich=true') ?>
251     <?php echo input_date_tag('test', '1 week 2 days 4 hours 2 seconds', 'rich=true') ?>
252
253     // Return null
254     <?php echo input_date_tag('test', null, 'rich=true') ?>
255     <?php echo input_date_tag('test', '', 'rich=true') ?>
256
257 Listing 10-5 - Incorrect Date Formats in Date Helpers
258
259     [php]
260     // Date zero = 01/01/1970
261     <?php echo input_date_tag('test', 0, 'rich=true') ?>
262
263     // Non-English date formats don't work
264     <?php echo input_date_tag('test', '01/04/2006', 'rich=true') ?>
265
266 ### Rich Text Editing
267
268 Rich text editing is also possible in a `<textarea>` tag, thanks to the integration of the TinyMCE and FCKEditor widgets. They provide a word-processor-like interface with buttons to format text as bold, italic, and other styles, as shown in Figure 10-2.
269
270 Figure 10-2 - Rich text editing
271
272 ![Rich text editing](/images/book/F1002.png "Rich text editing")
273
274 Both widgets require manual installation. As the procedure is the same for the two widgets, only the TinyMCE rich text editing is described here. You need to download the editor from the project website ([http://tinymce.moxiecode.com/](http://tinymce.moxiecode.com/)) and unpack it in a temporary folder. Copy the `tinymce/jscripts/tiny_mce/` directory into your project `web/js/` directory, and define the path to the library in `settings.yml`, as shown in Listing 10-6.
275
276 Listing 10-6 - Setting Up the TinyMCE Library Path
277
278     all:
279       .settings:
280         rich_text_js_dir:  js/tiny_mce
281
282 Once this is done, toggle the use of rich text editing in text areas by adding the `rich=true` option. You can also specify custom options for the JavaScript editor using the `tinymce_options` option. Listing 10-7 shows examples.
283
284 Listing 10-7 - Rich Text Area
285
286     [php]
287     <?php echo textarea_tag('name', 'default content', 'rich=true size=10x20')) ?>
288      => a rich text edit zone powered by TinyMCE
289     <?php echo textarea_tag('name', 'default content', 'rich=true size=10x20 tinymce_options=language:"fr",theme_advanced_buttons2:"separator"')) ?>
290     => a rich text edit zone powered by TinyMCE with custom parameters
291
292 ### Country and Language Selection
293
294 You may need to display a country selection field. But since country names are not the same in all languages, the options of a country drop-down list should vary according to the user culture (see Chapter 13 for more information about cultures). As shown in Listing 10-8, the `select_country_tag()` helper does it all for you: It internationalizes country names and uses the standard ISO country codes for values.
295
296 Listing 10-8 - Select Country Tag Helper
297
298     [php]
299     <?php echo select_country_tag('country', 'AL') ?>
300      => <select name="country" id="country">
301           <option value="AF">Afghanistan</option>
302           <option value="AL" selected="selected">Albania</option>
303           <option value="DZ">Algeria</option>
304           <option value="AS">American Samoa</option>
305       ...
306
307 Similar to `select_country_tag()` helper, the `select_language_tag()` helper displays a list of languages, as shown in Listing 10-9.
308
309 Listing 10-9 - Select Language Tag Helper
310
311     [php]
312     <?php echo select_language_tag('language', 'en') ?>
313      => <select name="language" id="language">
314           ...
315           <option value="elx">Elamite</option>
316           <option value="en" selected="selected">English</option>
317           <option value="enm">English, Middle (1100-1500)</option>
318           <option value="ang">English, Old (ca.450-1100)</option>
319           <option value="myv">Erzya</option>
320           <option value="eo">Esperanto</option>
321           ...
322
323 Form Helpers for Objects
324 ------------------------
325
326 When form elements are used to edit the properties of an object, standard link helpers can become tedious to write. For instance, to edit the `telephone` attribute of a `Customer` object, you would write this:
327
328     [php]
329     <?php echo input_tag('telephone', $customer->getTelephone()) ?>
330     => <input type="text" name="telephone" id="telephone" value="0123456789" />
331
332 To avoid repeating the attribute name, symfony provides an alternative object form helper for each form helper. An object form helper deduces the name and the default value of a form element from an object and a method name. The previous `input_tag()` is equivalent to this:
333
334     [php]
335     <?php echo object_input_tag($customer, 'getTelephone') ?>
336     => <input type="text" name="telephone" id="telephone" value="0123456789" />
337
338 The economy might not look crucial for the `object_input_tag()`. However, every standard form helper has a corresponding object form helper, and they all share the same syntax. It makes generation of forms quite straightforward. That's why the object form helpers are used extensively in the scaffolding and generated administrations (see Chapter 14). Listing 10-10 lists the object form helpers.
339
340 Listing 10-10 - Object Form Helpers Syntax
341
342     [php]
343     <?php echo object_input_tag($object, $method, $options) ?>
344     <?php echo object_input_date_tag($object, $method, $options) ?>
345     <?php echo object_input_hidden_tag($object, $method, $options) ?>
346     <?php echo object_textarea_tag($object, $method, $options) ?>
347     <?php echo object_checkbox_tag($object, $method, $options) ?>
348     <?php echo object_select_tag($object, $method, $options) ?>
349     <?php echo object_select_country_tag($object, $method, $options) ?>
350     <?php echo object_select_language_tag($object, $method, $options) ?>
351
352 There is no `object_password_tag()` helper, since it is a bad practice to give a default value to a password tag, based on something the user has previously entered.
353
354 >**CAUTION**
355 >Unlike the regular form helpers, the object form helpers are available only if you declare explicitly the use of the `Object` helper group in your template with `use_helper('Object')`.
356
357 The most interesting of all object form helpers are `objects_for_select()` and `object_select_tag()`, which concern drop-down lists.
358
359 ### Populating Drop-Down Lists with Objects
360
361 The `options_for_select()` helper, described previously with the other standard helpers, transforms a PHP associative array into an options list, as shown in Listing 10-11.
362
363 Listing 10-11 - Creating a List of Options Based on an Array with `options_for_select()`
364
365     [php]
366     <?php echo options_for_select(array(
367       '1' => 'Steve',
368       '2' => 'Bob',
369       '3' => 'Albert',
370       '4' => 'Ian',
371       '5' => 'Buck'
372     ), 4) ?>
373      => <option value="1">Steve</option>
374         <option value="2">Bob</option>
375         <option value="3">Albert</option>
376         <option value="4" selected="selected">Ian</option>
377         <option value="5">Buck</option>
378
379 Suppose that you already have an array of objects of class `Author`, resulting from a Propel query. If you want to build a list of options based on this array, you will need to loop on it to retrieve the `id` and the `name` of each object, as shown in Listing 10-12.
380
381 Listing 10-12 - Creating a List of Options Based on an Array of Objects with `options_for_select()`
382
383     [php]
384     // In the action
385     $options = array();
386     foreach ($authors as $author)
387     {
388       $options[$author->getId()] = $author->getName();
389     }
390     $this->options = $options;
391
392     // In the template
393     <?php echo options_for_select($options, 4) ?>
394
395 This kind of processing happens so often that symfony has a helper to automate it: `objects_for_select()`, which creates an option list based directly on an array of objects. The helper needs two additional parameters: the method names used to retrieve the `value` and the text contents of the `<option>` tags to be generated. So Listing 10-12 is equivalent to this simpler form:
396
397     [php]
398     <?php echo objects_for_select($authors, 'getId', 'getName', 4) ?>
399
400 That's smart and fast, but symfony goes even further, when you deal with foreign key columns.
401
402 ### Creating a Drop-Down List Based on a Foreign Key Column
403
404 The values a foreign key column can take are the primary key values of the foreign table records. If, for instance, the `article` table has an `author_id` column that is a foreign key to an `author` table, the possible values for this column are the `id` of all the records of the `author` table. Basically, a drop-down list to edit the author of an article would look like Listing 10-13.
405
406 Listing 10-13 - Creating a List of Options Based on a Foreign Key with `objects_for_select()`
407
408     [php]
409     <?php echo select_tag('author_id', objects_for_select(
410       AuthorPeer::doSelect(new Criteria()),
411       'getId',
412       '__toString',
413       $article->getAuthorId()
414     )) ?>
415     => <select name="author_id" id="author_id">
416           <option value="1">Steve</option>
417           <option value="2">Bob</option>
418           <option value="3">Albert</option>
419           <option value="4" selected="selected">Ian</option>
420           <option value="5">Buck</option>
421         </select>
422
423 The `object_select_tag()` does all that by itself. It displays a drop-down list populated with the name of the possible records of the foreign table. The helper can guess the foreign table and foreign column from the schema, so its syntax is very concise. Listing 10-13 is equivalent to this:
424
425     [php]
426     <?php echo object_select_tag($article, 'getAuthorId') ?>
427
428 The `object_select_tag()` helper guesses the related peer class name (`AuthorPeer` in the example) based on the method name passed as a parameter. However, you can specify your own class by setting the `related_class` option in the third argument. The text content of the `<option>` tags is the record name, which is the result of the `__toString()` method of the object class (if `$author->__toString()` method is undefined, the primary key is used instead). In addition, the list of options is built from a doSelect() method with an empty criteria value; it returns all the records ordered by creation date. If you prefer to display only a subset of records with a specific ordering, create a method in the peer class returning this selection as an array of objects, and set it in the `peer_method` option. Lastly, you can add a blank option or a custom option at the top of the drop-down list by setting the include_blank and include_custom options. Listing 10-14 demonstrates these different options for the `object_select_tag()` helper.
429
430 Listing 10-14 - Options of the `object_select_tag()` Helper
431
432     [php]
433     // Base syntax
434     <?php echo object_select_tag($article, 'getAuthorId') ?>
435     // Builds the list from AuthorPeer::doSelect(new Criteria())
436
437     // Change the peer class used to retrieve the possible values
438     <?php echo object_select_tag($article, 'getAuthorId', 'related_class=Foobar') ?>
439     // Builds the list from FoobarPeer::doSelect(new Criteria())
440
441     // Change the peer method used to retrieve the possible values
442     <?php echo object_select_tag($article, 'getAuthorId','peer_method=getMostFamousAuthors') ?>
443     // Builds the list from AuthorPeer::getMostFamousAuthors(new Criteria())
444
445     // Add an <option value="">&nbsp;</option> at the top of the list
446     <?php echo object_select_tag($article, 'getAuthorId', 'include_blank=true') ?>
447
448     // Add an <option value="">Choose an author</option> at the top of the list
449     <?php echo object_select_tag($article, 'getAuthorId',
450       'include_custom=Choose an author') ?>
451
452 ### Updating Objects
453
454 A form completely dedicated to editing object properties by using object helpers is easier to handle in an action. For instance, if you have an object of class `Author` with `name`, `age`, and `address` attributes, the form can be coded as shown in Listing 10-15.
455
456 Listing 10-15 - A Form with Only Object Helpers
457
458     [php]
459     <?php echo form_tag('author/update') ?>
460       <?php echo object_input_hidden_tag($author, 'getId') ?>
461       Name: <?php echo object_input_tag($author, 'getName') ?><br />
462       Age:  <?php echo object_input_tag($author, 'getAge') ?><br />
463       Address: <br />
464              <?php echo object_textarea_tag($author, 'getAddress') ?>
465     </form>
466
467 The `update` action of the `author` module, called when the form is submitted, can simply update the object with the `fromArray()` modifier generated by Propel, as shown in Listing 10-16.
468
469 Listing 10-16 - Handling a Form Submission Based on Object Form Helpers
470
471     [php]
472     public function executeUpdate ()
473     {
474       $author = AuthorPeer::retrieveByPk($this->getRequestParameter('id'));
475       $this->forward404Unless($author);
476
477       $author->fromArray($this->getRequest()->getParameterHolder()->getAll(),BasePeer::TYPE_FIELDNAME);
478       $author->save();
479
480       return $this->redirect('/author/show?id='.$author->getId());
481     }
482
483 Form Validation
484 ---------------
485
486 >**NOTE**
487 >The features described in this section are deprecated in symfony 1.1 and only work if you enable the `sfCompat10` plugin.
488
489 Chapter 6 explained how to use the `validateXXX()` methods in the action class to validate the request parameters. However, if you use this technique to validate a form submission, you will end up rewriting the same portion of code over and over. Symfony provides an alternative form-validation technique, relying on only a YAML file, instead of PHP code in the action class.
490
491 To demonstrate the form-validation features, let's first consider the sample form shown in Listing 10-17. It is a classic contact form, with `name`, `email`, `age`, and `message` fields.
492
493 Listing 10-17 - Sample Contact Form, in `modules/contact/templates/indexSuccess.php`
494
495     [php]
496     <?php echo form_tag('contact/send') ?>
497       Name:    <?php echo input_tag('name') ?><br />
498       Email:   <?php echo input_tag('email') ?><br />
499       Age:     <?php echo input_tag('age') ?><br />
500       Message: <?php echo textarea_tag('message') ?><br />
501       <?php echo submit_tag() ?>
502     </form>
503
504 The principle of form validation is that if a user enters invalid data and submits the form, the next page should show an error message. Let's define what valid data should be for the sample form, in plain English:
505
506   * The `name` field is required. It must be a text entry between 2 and 100 characters.
507   * The `email` field is required. It must be a text entry between 2 and 100 characters, and it must be a valid e-mail address.
508   * The `age` field is required. It must be an integer between 0 and 120.
509   * The `message` field is required.
510
511 You could define more complex validation rules for the contact form, but these are just fine for a demonstration of the validation possibilities.
512
513 >**NOTE**
514 >Form validation can occur on the server side and/or on the client side. The server-side validation is compulsory to avoid corrupting a database with wrong data. The client-side validation is optional, though it greatly enhances the user experience. The client-side validation is to be done with custom JavaScript.
515
516 ### Validators
517
518 You can see that the `name` and `email` fields in the example share common validation rules. Some validation rules appear so often in web forms that symfony packages the PHP code that implements them into validators. A validator is simple class that provides an `execute()` method. This method expects the value of a field as parameter, and returns `true` if the value is valid and `false` otherwise.
519
520 Symfony ships with several validators (described in the "Standard Symfony Validators" section later in this chapter), but let's focus on the `sfStringValidator` for now. This validator checks that an input is a string, and that its size is between two specified character amounts (defined when calling the `initialize()` method). That's exactly what is required to validate the `name` field. Listing 10-18 shows how to use this validator in a validation method.
521
522 Listing 10-18 - Validating Request Parameters with Reusable Validators, in `modules/contact/action/actions.class.php`
523
524     [php]
525     public function validateSend()
526     {
527       $name = $this->getRequestParameter('name');
528
529       // The name field is required
530       if (!$name)
531       {
532         $this->getRequest()->setError('name', 'The name field cannot be left blank');
533
534         return false;
535       }
536
537       // The name field must be a text entry between 2 and 100 characters
538       $myValidator = new sfStringValidator($this->getContext(), array(
539         'min'       => 2,
540         'min_error' => 'This name is too short (2 characters minimum)',
541         'max'       => 100,
542         'max_error' => 'This name is too long. (100 characters maximum)',
543       ));
544       if (!$myValidator->execute($name, $error))
545       {
546         return false;
547       }
548
549       return true;
550     }
551
552 If a user submits the form in Listing 10-17 with the value `a` in the `name` field, the `execute()` method of the `sfStringValidator` will return `false` (because the string length is less than the minimum of two characters). The `validateSend()` method will then fail, and the `handleErrorSend()` method will be called instead of the `executeSend()` method.
553
554 >**TIP**
555 >The `setError()` method of the `sfRequest` method gives information to the template so that it can display an error message (as explained in the "Displaying the Error Messages in the Form" section later in this chapter). The validators set the errors internally, so you can define different errors for the different cases of nonvalidation. That's the purpose of the `min_error` and `max_error` initialization parameters of the `sfStringValidator`.
556
557 All the rules defined in the example can be translated into validators:
558
559   * `name`: `sfStringValidator` (`min=2`, `max=100`)
560   * `email`: `sfStringValidator` (`min=2`, `max=100`) and `sfEmailValidator`
561   * `age`: `sfNumberValidator` (`min=0`, `max=120`)
562
563 The fact that a field is required is not handled by a validator.
564
565 ### Validation File
566
567 You could easily implement the validation of the contact form with validators in the `validateSend()` method PHP, but that would imply repeating a lot of code. Symfony offers an alternative way to define validation rules for a form, and it involves YAML. For instance, Listing 10-19 shows the translation of the name field validation rules, and its results are equivalent to those of Listing 10-18.
568
569 Listing 10-19 - Validation File, in `modules/contact/validate/send.yml`
570
571     fields:
572       name:
573         required:
574           msg:       The name field cannot be left blank
575         sfStringValidator:
576           min:       2
577           min_error: This name is too short (2 characters minimum)
578           max:       100
579           max_error: This name is too long. (100 characters maximum)
580
581 In a validation file, the `fields` header lists the fields that need to be validated, if they are required, and the validators that should be tested on them when a value is present. The parameters of each validator are the same as those you would use to initialize the validator manually. A field can be validated by as many validators as necessary.
582
583 >**NOTE**
584 >The validation process doesn't stop when a validator fails. Symfony tests all the validators and declares the validation failed if at least one of them fails. And even if some of the rules of the validation file fail, symfony will still look for a `validateXXX()` method and execute it. So the two validation techniques are complementary. The advantage is that, in a form with multiple failures, all the error messages are shown.
585
586 Validation files are located in the module `validate/` directory, and named by the action they must validate. For example, Listing 10-19 must be stored in a file called `validate/send.yml`.
587
588 ### Redisplaying the Form
589
590 By default, symfony looks for a `handleErrorSend()` method in the action class whenever the validation process fails, or displays the `sendError.php` template if the method doesn't exist.
591
592 The usual way to inform the user of a failed validation is to display the form again with an error message. To that purpose, you need to override the `handleErrorSend()` method and end it with a redirection to the action that displays the form (in the example, `module/index`), as shown in Listing 10-20.
593
594 Listing 10-20 - Displaying the Form Again, in `modules/contact/actions/actions.class.php`
595
596     [php]
597     class ContactActions extends sfActions
598     {
599       public function executeIndex()
600       {
601         // Display the form
602       }
603
604       public function handleErrorSend()
605       {
606         $this->forward('contact', 'index');
607       }
608
609       public function executeSend()
610       {
611         // Handle the form submission
612       }
613     }
614
615 If you choose to use the same action to display the form and handle the form submission, then the `handleErrorSend()` method can simply return `sfView::SUCCESS` to redisplay the form from `sendSuccess.php`, as shown in Listing 10-21.
616
617 Listing 10-21 - A Single Action to Display and Handle the Form, in `modules/contact/actions/actions.class.php`
618
619     [php]
620     class ContactActions extends sfActions
621     {
622       public function executeSend()
623       {
624         if (!$this->getRequest()->isMethod('post'))
625         {
626           // Prepare data for the template
627
628           // Display the form
629           return sfView::SUCCESS;
630         }
631         else
632         {
633           // Handle the form submission
634           ...
635           $this->redirect('mymodule/anotheraction');
636         }
637       }
638       public function handleErrorSend()
639       {
640         // Prepare data for the template
641
642         // Display the form
643         return sfView::SUCCESS;
644       }
645     }
646
647 The logic necessary to prepare the data can be refactored into a protected method of the action class, to avoid repeating it in the `executeSend()` and `handleErrorSend()` methods.
648
649 With this new configuration, when the user types an invalid name, the form is displayed again, but the entered data is lost and no error message explains the reason of the failure. To address the last issue, you must modify the template that displays the form, to insert error messages close to the faulty field.
650
651 ### Displaying the Error Messages in the Form
652
653 The error messages defined as validator parameters are added to the request when a field fails validation (just as you can add an error manually with the `setError()` method, as in Listing 10-18). The `sfRequest` object provides two useful methods to retrieve the error message: `hasError()` and `getError()`, which each expect a field name as parameter. In addition, you can display an alert at the top of the form to draw attention to the fact that one or many of the fields contain invalid data with the `hasErrors()` method. Listings 10-22 and 10-23 demonstrate how to use these methods.
654
655 Listing 10-22 - Displaying Error Messages at the Top of the Form, in `templates/indexSuccess.php`
656
657     [php]
658     <?php if ($sf_request->hasErrors()): ?>
659       <p>The data you entered seems to be incorrect.
660       Please correct the following errors and resubmit:</p>
661       <ul>
662       <?php foreach($sf_request->getErrors() as $name => $error): ?>
663         <li><?php echo $name ?>: <?php echo $error ?></li>
664       <?php endforeach; ?>
665       </ul>
666     <?php endif; ?>
667
668 Listing 10-23 - Displaying Error Messages Inside the Form, in `templates/indexSuccess.php`
669
670     [php]
671     <?php echo form_tag('contact/send') ?>
672       <?php if ($sf_request->hasError('name')): ?>
673         <?php echo $sf_request->getError('name') ?> <br />
674       <?php endif; ?>
675       Name:    <?php echo input_tag('name') ?><br />
676       ...
677       <?php echo submit_tag() ?>
678     </form>
679
680 The conditional use of the `getError()` method in Listing 10-23 is a bit long to write. That's why symfony offers a `form_error()` helper to replace it, provided that you declare the use of its helper group, `Validation`. Listing 10-24 replaces Listing 10-23 by using this helper.
681
682 Listing 10-24 - Displaying Error Messages Inside the Form, the Short Way
683
684     [php]
685     <?php use_helper('Validation') ?>
686     <?php echo form_tag('contact/send') ?>
687
688                <?php echo form_error('name') ?><br />
689       Name:    <?php echo input_tag('name') ?><br />
690       ...
691       <?php echo submit_tag() ?>
692     </form>
693
694 The `form_error()` helper adds a special character before and after each error message to make the messages more visible. By default, the character is an arrow pointing down (corresponding to the `&darr;` entity), but you can change it in the `settings.yml` file:
695
696     all:
697       .settings:
698         validation_error_prefix:    ' &darr;&nbsp;'
699         validation_error_suffix:    ' &nbsp;&darr;'
700
701 In case of failed validation, the form now displays errors correctly, but the data entered by the user is lost. You need to repopulate the form to make it really user-friendly.
702
703 ### Repopulating the Form
704
705 As the error handling is done through the `forward()` method (shown in Listing 10-20), the original request is still accessible, and the data entered by the user is in the request parameters. So you could repopulate the form by adding default values to each field, as shown in Listing 10-25.
706
707 Listing 10-25 - Setting Default Values to Repopulate the Form When Validation Fails, in `templates/indexSuccess.php`
708
709     [php]
710     <?php use_helper('Validation') ?>
711     <?php echo form_tag('contact/send') ?>
712                <?php echo form_error('name') ?><br />
713       Name:    <?php echo input_tag('name', $sf_params->get('name')) ?><br />
714                <?php echo form_error('email') ?><br />
715       Email:   <?php echo input_tag('email', $sf_params->get('email')) ?><br />
716                <?php echo form_error('age') ?><br />
717       Age:     <?php echo input_tag('age', $sf_params->get('age')) ?><br />
718                <?php echo form_error('message') ?><br />
719       Message: <?php echo textarea_tag('message', $sf_params->get('message')) ?><br />
720       <?php echo submit_tag() ?>
721     </form>
722
723 But once again, this is quite tedious to write. Symfony provides an alternative way of triggering repopulation for all the fields of a form, directly in the YAML validation file, without changing the default values of the elements. Just enable the `fillin:` feature for the form, with the syntax described in Listing 10-26.
724
725 Listing 10-26 - Activating `fillin` to Repopulate the Form When Validation Fails, in `validate/send.yml`
726
727     fillin:
728       enabled: true  # Enable the form repopulation
729       param:
730         name: test  # Form name, not needed if there is only one form in the page
731         skip_fields:   [email]  # Do not repopulate these fields
732         exclude_types: [hidden, password] # Do not repopulate these field types
733         check_types:   [text, checkbox, radio, password, hidden] # Do repopulate these
734
735 By default, the automatic repopulation works for text inputs, check boxes, radio buttons, text areas, and select components (simple and multiple), but it does not repopulate password or hidden tags. The `fillin` feature doesn't work for file tags.
736
737 >**NOTE**
738 >The `fillin` feature works by parsing the response content in XML just before sending it to the user. If the response is not a valid XHTML document, `fillin` might not work.
739
740 You might want to transform the values entered by the user before writing them back in a form input. Escaping, URL rewriting, transformation of special characters into entities, and all the other transformations that can be called through a function can be applied to the fields of your form if you define the transformation under the `converters:` key, as shown in Listing 10-27.
741
742 Listing 10-27 - Converting Input Before `fillin`, in `validate/send.yml`
743
744     fillin:
745       enabled: true
746       param:
747         name: test
748         converters:         # Converters to apply
749           htmlentities:     [first_name, comments]
750           htmlspecialchars: [comments]
751
752 ### Standard Symfony Validators
753
754 Symfony contains some standard validators that can be used for your forms:
755
756   * `sfStringValidator`
757   * `sfNumberValidator`
758   * `sfEmailValidator`
759   * `sfUrlValidator`
760   * `sfRegexValidator`
761   * `sfCompareValidator`
762   * `sfPropelUniqueValidator`
763   * `sfFileValidator`
764   * `sfCallbackValidator`
765
766 Each has a default set of parameters and error messages, but you can easily override them through the `initialize()` validator method or in the YAML file. The following sections describe the validators and show usage examples.
767
768 #### String Validator
769
770 `sfStringValidator` allows you to apply string-related constraints to a parameter.
771
772     sfStringValidator:
773       values:       [foo, bar]
774       values_error: The only accepted values are foo and bar
775       insensitive:  false  # If true, comparison with values is case insensitive
776       min:          2
777       min_error:    Please enter at least 2 characters
778       max:          100
779       max_error:    Please enter less than 100 characters
780
781 #### Number Validator
782
783 `sfNumberValidator` verifies if a parameter is a number and allows you to apply size constraints.
784
785     sfNumberValidator:
786       nan_error:    Please enter an integer
787       min:          0
788       min_error:    The value must be at least zero
789       max:          100
790       max_error:    The value must be less than or equal to 100
791
792 #### E-Mail Validator
793
794 `sfEmailValidator` verifies if a parameter contains a value that qualifies as an e-mail address.
795
796     sfEmailValidator:
797       strict:       true
798       email_error:  This email address is invalid
799
800 RFC822 defines the format of e-mail addresses. However, it is more permissive than the generally accepted format. For instance, `me@localhost` is a valid e-mail address according to the RFC, but you probably don't want to accept it. When the `strict` parameter is set to `true` (its default value), only e-mail addresses matching the pattern `name@domain.extension` are valid. When set to `false`, RFC822 is used as a rule.
801
802 #### URL Validator
803
804 `sfUrlValidator` checks if a field is a correct URL.
805
806     sfUrlValidator:
807       url_error:    This URL is invalid
808
809 #### Regular Expression Validator
810
811 `sfRegexValidator` allows you to match a value against a Perl-compatible regular expression pattern.
812
813     sfRegexValidator:
814       match:        No
815       match_error:  Posts containing more than one URL are considered as spam
816       pattern:      /http.*http/si
817
818 The `match` parameter determines if the request parameter must match the pattern to be valid (value `Yes`) or match the pattern to be invalid (value `No`).
819
820 #### Compare Validator
821
822 `sfCompareValidator` compares two different request parameters. It is very useful for password checks.
823
824     fields:
825       password1:
826         required:
827           msg:      Please enter a password
828       password2:
829         required:
830           msg:      Please retype the password
831         sfCompareValidator:
832           check:    password1
833           compare_error: The two passwords do not match
834
835 The `check` parameter contains the name of the field that the current field must match to be valid.
836
837 By default, the validator checks the equality of the parameters. You can change this behavior be specifying an `operator` parameter. Available operators are: `>`, `>=`, `<`, `<=`, `==` and `!=`.
838
839 #### Propel Unique Validator
840
841 `sfPropelUniqueValidator` validates that the value of a request parameter doesn't already exist in your database. It is very useful for unique indexes.
842
843     fields:
844       nickname:
845         sfPropelUniqueValidator:
846           class:        User
847           column:       login
848           unique_error: This login already exists. Please choose another one.
849
850 In this example, the validator will look in the database for a record of class `User` where the `login` column has the same value as the field to validate.
851
852 >**CAUTION**
853 >sfPropelUniqueValidator is susceptible to race conditions.  Although unlikely, in multiuser environments, the result may change the instant it returns.  You should still be ready to handle a duplicate INSERT error. 
854
855 #### File Validator
856
857 `sfFileValidator` applies format (an array of mime-types) and size constraints to file upload fields.
858
859     fields:
860       image:
861         file:       True
862         required:
863           msg:      Please upload an image file
864         sfFileValidator:
865           mime_types:
866             - 'image/jpeg'
867             - 'image/png'
868             - 'image/x-png'
869             - 'image/pjpeg'
870           mime_types_error: Only PNG and JPEG images are allowed
871           max_size:         512000
872           max_size_error:   Max size is 512Kb
873
874 Be aware that the `file` attribute must be set to `True` for the field, and the template must declare the form as multipart.
875
876 #### Callback Validator
877
878 `sfCallbackValidator` delegates the validation to a third-party callable method or function to do the validation. The callable method or function must return `true` or `false`.
879
880     fields:
881       account_number:
882         sfCallbackValidator:
883           callback:      is_numeric
884           invalid_error: Please enter a number.
885       credit_card_number:
886         sfCallbackValidator:
887           callback:      [myTools, validateCreditCard]
888           invalid_error: Please enter a valid credit card number.
889
890 The callback method or function receives the value to be validated as a first parameter. This is very useful when you want to reuse existing methods of functions, rather than create a full validator class.
891
892 >**TIP**
893 >You can also write your own validators, as described in the "Creating a Custom Validator" section later in this chapter.
894
895 ### Named Validators
896
897 If you see that you need to repeat a validator class and its settings, you can package it under a named validator. In the example of the contact form, the email field needs the same sfStringValidator parameters as the `name` field. So you can create a `myStringValidator` named validator to avoid repeating the same settings twice. To do so, add a `myStringValidator` label under the `validators:` header, and set the `class` and `param` keys with the details of the named validator you want to package. You can then use the named validator just like a regular one in the `fields` section, as shown in Listing 10-28.
898
899 Listing 10-28 - Reusing Named Validators in a Validation File, in `validate/send.yml`
900
901     validators:
902       myStringValidator:
903         class: sfStringValidator
904         param:
905           min:       2
906           min_error: This field is too short (2 characters minimum)
907           max:       100
908           max_error: This field is too long (100 characters maximum)
909
910     fields:
911       name:
912         required:
913           msg:       The name field cannot be left blank
914         myStringValidator:
915       email:
916         required:
917           msg:       The email field cannot be left blank
918         myStringValidator:
919         sfEmailValidator:
920           email_error:  This email address is invalid
921
922 ### Restricting the Validation to a Method
923
924 By default, the validators set in a validation file are run when the action is called with the POST method. You can override this setting globally or field by field by specifying another value in the `methods` key, to allow a different validation for different methods, as shown in Listing 10-29.
925
926 Listing 10-29 - Defining When to Test a Field, in `validate/send.yml`
927
928     methods:         [post]     # This is the default setting
929
930     fields:
931       name:
932         required:
933           msg:       The name field cannot be left blank
934         myStringValidator:
935       email:
936         methods:     [post, get] # Overrides the global methods settings
937         required:
938           msg:       The email field cannot be left blank
939         myStringValidator:
940         sfEmailValidator:
941           email_error:  This email address is invalid
942
943 ### What Does a Validation File Look Like?
944
945 So far, you have seen only bits and pieces of a validation file. When you put everything together, the validation rules find a clear translation in YAML. Listing 10-30 shows the complete validation file for the sample contact form, corresponding to all the rules defined earlier in the chapter.
946
947 Listing 10-30 - Sample Complete Validation File
948
949     fillin:
950       enabled:      true
951
952     validators:
953       myStringValidator:
954         class: sfStringValidator
955         param:
956           min:       2
957           min_error: This field is too short (2 characters minimum)
958           max:       100
959           max_error: This field is too long (100 characters maximum)
960
961     fields:
962       name:
963         required:
964           msg:       The name field cannot be left blank
965         myStringValidator:
966       email:
967         required:
968           msg:       The email field cannot be left blank
969         myStringValidator:
970         sfEmailValidator:
971           email_error:  This email address is invalid
972       age:
973         sfNumberValidator:
974           nan_error:    Please enter an integer
975           min:          0
976           min_error:    "You're not even born. How do you want to send a message?"
977           max:          120
978           max_error:    "Hey, grandma, aren't you too old to surf on the Internet?"
979       message:
980         required:
981           msg:          The message field cannot be left blank
982
983 Complex Validation
984 ------------------
985
986 The validation file satisfies most needs, but when the validation is very complex, it might not be sufficient. In this case, you can still return to the `validateXXX()` method in the action, or find the solution to your problem in the following sections.
987
988 ### Creating a Custom Validator
989
990 Each validator is a class that extends the `sfValidator` class. If the validator classes shipped with symfony are not suitable for your needs, you can easily create a new one, in any of the `lib/` directories where it can be autoloaded. The syntax is quite simple: The `execute()` method of the validator is called when the validator is executed. You can also define default settings in the `initialize()` method.
991
992 The `execute()` method receives the value to validate as the first parameter and the error message to throw as the second parameter. Both are passed as references, so you can modify the error message from within the method.
993
994 The `initialize()` method receives the context singleton and the array of parameters from the YAML file. It must first call the `initialize()` method of its parent `sfValidator` class, and then set the default values.
995
996 Every validator has a parameter holder accessible by `$this->getParameterHolder()`.
997
998 For instance, if you want to build an `sfSpamValidator` to check if a string is not spam, add the code shown in Listing 10-31 to an `sfSpamValidator.class.php` file. It checks if the `$value` contains more than `max_url` times the string `'http'`.
999
1000 Listing 10-31 - Creating a Custom Validator, in `lib/sfSpamValidator.class.php`
1001
1002     [php]
1003     class sfSpamValidator extends sfValidator
1004     {
1005       public function execute (&$value, &$error)
1006       {