Development

Documentation/hu_HU/forms_book/1.1/01-Form-Creation

You must first sign up to be able to contribute.

Version 1 (modified by blerou, 9 years ago)
piszkozat, r9798

1. fejezet - Űrlap készítés

Egy űrlap bizonyos mezők (rejtett, beviteli, szövegdoboz, lenyíló list, ...) összessége. Ebben a fejezetben az űrlap készítését és űrlapmezők kezelését mutatjuk be az symfony űrlap keretrendszerének segítségével.

A következő fejezetekben található leírás a symfony 1.1 verziójáról szól. Szükség lesz még egy projektre és azon belül egy frontend alkalmazásra. Symfony projekt készítéséről további információ a könyv ide vonatkozó fejezetében található.

Előkészítés

Egy kapcsolat űrlap felvételével kezdjük.

1-1 ábra mutatja az űrlapot, amin keresztül a felhasználó üzenetet küldhet nekünk.

1-1 ábra - Kapcsolat űrlap

Kapcsolat űrlap

Három mezőt használunk az űrlapon: a felhasználó nevét, az email címét és az üzenetet, amit elküld nekünk. Ezután egyszerűen megjelenítjük az elküldött adatokat, ahogy az 1-2 ábrán látható.

1-2 ábra - Üzenet oldal

Üzenet oldal

1-3 ábra - a felhasználó és az alkalmazás közötti kölcsönhatást mutatja

Figure 1-3 - Kölcsönhatás a felhasználóval

Kölcsönhatás a felhasználóval séma

Widgetek

sfForm és sfWidget osztályok

Az űrlapot mezők alkotják, melyeket a felhasználó tölt ki adatokkal. A symfonyban az űrlap egy objektum, mely az sfForm osztályból származik. Példánkban létrehoztunk egy ContactForm osztályt, melynek őse az sfForm. Az sfForm minden űrlap ősosztálya. Az sfForm teszi lehetővé űrlapjaink egyszerű kezelését és konfigurálását.

Note Az sfForm osztály minden űrlap ősosztálya, mely lehetővé teszi azok egyszerű kezelését és konfigurálást.

Az űrlaphoz a configure() metóduson keresztül adhatunk widgeteket.

Egy widget egy űrlap mezőt ábrázol. Példánknál maradva három widgetre lesz szükségünk a három mező ábrázolásához: name, email és message. Az 1-1 jegyzet mutatja be a ContactForm első megvalósítását.

1-1 jegyzet - ContactForm osztály három mezővel

[php]
// lib/form/ContactForm.class.php
class ContactForm extends sfForm
{
  public function configure()
  {
    $this->setWidgets(array(
      'name'    => new sfWidgetFormInput(),
      'email'   => new sfWidgetFormInput(),
      'message' => new sfWidgetFormTextarea(),
    ));
  }
}

A widgetek a configure() metóduson belül vannak definiálva. Ez a metódus automatikusan meghívásra kerül az sfForm osztály konstruktorában.

A setWidgets() metódussal lehet az űrlapon használt widgeteket definiálni. A setWidgets() metódus egy asszociatív tömböt vár, ahol a kulcsok jelölik az egyes mezők nevét, az értékek pedig a widget objektumokat. Minden widget egy objektum, melynek az sfWidget osztály az őse. Példánkban két típusú widgetet használunk:

  • sfWidgetFormInput : ez a widget egy input mezőt ábrázol
  • sfWidgetFormTextarea: ez a widget egy szövegdobozt (textarea) ábrázol

Note Megegyezés szerint a form osztályok a lib/form/ könyvtárban találhatók. Igazság szerint az űrlapok bárhol tárolhatók, ahol a symfony autoloading mechanizmusa megtalálja azokat, de később látni fogjuk, hogy a rendszer a lib/form/ könyvtárba hozza létre a model objektumokból generált űrlapokat.

Displaying the Form

Űrlapok megjelenítése

Űrlapunk használatra készen áll. Már csak létre kell hozni egy modult, hogy megjelenítsük azt.

$ cd ~/PATH/TO/THE/PROJECT
$ php symfony generate:module frontend contact

A contact modul index action-ét módosítsuk, hogy a form példányt átadjuk a templatenek, ahogy az 1-2 jegyzeten látható.

1-2 jegyzet - Action osztály a contact modulban

[php]
// apps/frontend/modules/contact/actions/actions.class.php
class contactActions extends sfActions
{
  public function executeIndex()
  {
    $this->form = new ContactForm();
  }
}

Mikor létrehozzuk az űrlapot, a korábban definiált configure() metódus automatikusan lefut.

Már csak egy templatere van szükségünk, hogy megjelenjen űrlapunk.

1-3 jegyzet - Űrlap megjelenítése templateben

[php]
// apps/frontend/modules/contact/templates/indexSuccess.php
<form action="<?php echo url_for('contact/submit') ?>" method="POST">
  <table>
    <?php echo $form ?>
    <tr>
      <td colspan="2">
        <input type="submit" />
      </td>
    </tr>
  </table>
</form>

Az űrlap csak a felhasználó számára fontos információt kezeli. Az indexSuccess templateben a <?php echo $form ?> sor csak három mezőt jelenít meg. A többi elemet, mint a form tag és a submit gomb magunknak kell hozzáadni. Első pillantásra ez nem túl nyilvánvaló, viszont később látni fogjuk milyen hasznos, ha az űrlapjaink között átfedések vannak.

A <?php echo $form ?> használata rendkívül hasznos űrlap prototípusok készítésénél. Lehetővé teszi, hogy a fejlesztő az üzleti logikára koncentráljon anélkül, hogy a megjelenés miatt keljen aggódnia. A 3. fejezet mutatja be az űrlapok megjelenésének testreszabását.

Note

Mikor a <?php echo $form ?> formát használjuk megjelenítésre, a PHP a $form objektum szöveges alakját jeleníti meg. Az objektum szöveggé konvertálásakor a PHP a __toString() magic metódust próbálja meg futtatni. Minden widget megvalósítja ezt, hogy az objektum HTML alakját vissza tudja adni. A <?php echo $form ?> meghívása egyenértékű a <?php echo $form->__toString() ?> hívással.

Most már meg tudjuk jeleníteni az űrlapot a böngészőben (1-4 ábra) és ellenőrizhetük az eredményt a contact/index action begépelésével (/frontend_dev.php/contact).

Figure 1-4 - Generated Contact Form 1-4 ábra - Generált kapcsolat űrlap

Generált kapcsolat űrlap

Listing 1-4 Shows the generated code by the template. 1-4 jegyzet A template által generált kód

[html]
<form action="/frontend_dev.php/contact/submit" method="POST">
  <table>

    <!-- A <?php echo $form ?> által generált űrlap kezdete -->
    <tr>
      <th><label for="name">Name</label></th>
      <td><input type="text" name="name" id="name" /></td>
    </tr>
    <tr>
      <th><label for="email">Email</label></th>
      <td><input type="text" name="email" id="email" /></td>
    </tr>
    <tr>
      <th><label for="message">Message</label></th>
      <td><textarea rows="4" cols="30" name="message" id="message"></textarea></td>
    </tr>
    <!-- A <?php echo $form ?> által generált űrlap vége -->

    <tr>
      <td colspan="2">
        <input type="submit" />
      </td>
    </tr>
  </table>
</form>

Azt láthatjuk, hogy az űrlap egy HTML táblázat három <tr> sorban jelenik meg. Ezért tettük az egészet egy <table> tagba. Minden sor tartalmaz egy <label> taget és egy űrlap taget (<input> vagy <textarea>).

Cimkék

Minden mezőhöz automatikusan létrejön egy cimke. Alapbeállításban a cimke nevet a mező nevéből képzi a rendszer a következő szabályok alapján: az első betű nagy betű, az aláhúzások cseréje szóközökkel. Példa:

[php]
$this->setWidgets(array(
  'first_name' => new sfWidgetFormInput(), // generált cimke: "First name"
  'last_name'  => new sfWidgetFormInput(), // generált cimke: "Last name"
));

Habár az automatikus cimke generálás nagyon hasznos, a keretrendszer megengedi a cimkék testreszabását is a setLabels() metóduson keresztül:

[php]
$this->widgetSchema->setLabels(array(
  'name'    => 'Your name',
  'email'   => 'Your email address',
  'message' => 'Your message',
));

A setLabel() metóduson keresztül az egyes mezők cimkéje is módosítható:

[php]
$this->widgetSchema->setLabel('email', 'Your email address');

Végül, a 3. fejezetben látni fogjuk, hogyan terjeszthetjük ki a cimkéket a templatekben, tovább finomítva az űrlapon.

Sidebar Widget Séma

Mikor a setWidgets() metódust használjuk, a symfony létrehoz egy sfWidgetFormSchema objektumot. Ez az objetum is egy widget, amely widgetek egy halmazát ábrázolja. A ContactForm űrlapunkban használtunk a setWidgets() metódust. Ez egyenértékű a következő kóddal:

[php]
$this->setWidgetSchema(new sfWidgetFormSchema(array(
  'name'    => new sfWidgetFormInput(),
  'email'   => new sfWidgetFormInput(),
  'message' => new sfWidgetFormTextarea(),
)));

// majdnem egyenértékű a :

$this->widgetSchema = new sfWidgetFormSchema(array(
  'name'    => new sfWidgetFormInput(),
  'email'   => new sfWidgetFormInput(),
  'message' => new sfWidgetFormTextarea(),
));

The setLabels() method is applied to a collection of widgets included in the widgetSchema object . A setLabels() metódus használatakor azt widgetek egy csoportján alkalmazzuk, melyeket a widgetSchema objektum tartalmmazza.

Az 5. fejezetben látjuk majd, hogy a "schema widget" használata mennyire leegyszerűsíti az egymást átfedő űrlapok használatát.

A generált táblázatokon túl

Even if the form display is an HTML table by default, the layout format can be changed. These different types of layout formats are defined in classes inheriting from sfWidgetFormSchemaFormatter. By default, a form uses the table format as defined in the sfWidgetFormSchemaFormatterTable class. You can also use the list format: Habár az űrlap HTML táblázatként jelenik meg alapból, a layout megváltoztatható.

[php]
class ContactForm extends sfForm
{
  public function configure()
  {
    $this->setWidgets(array(
      'name'    => new sfWidgetFormInput(),
      'email'   => new sfWidgetFormInput(),
      'message' => new sfWidgetFormTextarea(),
    ));

    $this->widgetSchema->setFormFormatterName('list');
  }
}

Those two formats come by default and we will see in Chapter 5 how to create your own format classes. Now that we know how to display a form, let's see how to manage the submission.

Submitting the Form

When we created a template to display a form, we used the internal URL contact/submit in the form tag to submit the form. We now need to add the submit action in the contact module. Listing 1-5 shows how an action can get the information from the user and redirect to the thank you page where we just display this information back to the user.

Listing 1-5 - Use of the submit action in the contact module

[php]
public function executeSubmit($request)
{
  $this->forward404Unless($request->isMethod('post'));

  $params = array(
    'name'    => $request->getParameter('name'),
    'email'   => $request->getParameter('email'),
    'message' => $request->getParameter('message'),
  );

  $this->redirect('contact/thankyou?'.http_build_query($params));
}

public function executeThankyou()
{
}

// apps/frontend/modules/contact/templates/thankyouSuccess.php
<ul>
  <li>Name:    <?php echo $sf_params->get('name') ?></li>
  <li>Email:   <?php echo $sf_params->get('email') ?></li>
  <li>Message: <?php echo $sf_params->get('message') ?></li>
</ul>

Note http_build_query is a built-in PHP function that generates a URL-encoded query string from an array of parameters.

executeSubmit() method executes three actions:

  • For security reasons, we check that the page has been submitted using the HTTP method POST. If not sent using the POST method then the user is redirected to a 404 page. In the indexSuccess template, we declared the submit method as POST (<form ... method="POST">):

    [php]
    $this->forward404Unless($request->isMethod('post'));
    
  • Next we get the values from the user input to store them in the params table:

    [php]
    $params = array(
      'name'    => $request->getParameter('name'),
      'email'   => $request->getParameter('email'),
      'message' => $request->getParameter('message'),
    );
    
  • Finally, we redirect the user to a Thank you page (contact/thankyou) to display his information:

    [php]
    $this->redirect('contact/thankyou?'.http_build_query($params));
    

Instead of redirecting the user to another page, we could have created a submitSuccess.php template. While it is possible, it is better practice to always redirect the user after a request with the POST method:

  • This prevents the form from being submitted again if the user reloads the Thank you page.

  • The user can also click on the back button without getting the pop-up to submit the form again.

Tip You might have noticed that executeSubmit() is different from executeIndex(). When calling these methods symfony passes the current sfRequest object as the first argument to the executeXXX() methods. With PHP, you do not have to collect all parameters, that is why we did not define the request variable in executeIndex() since we do not need it.

Figure 1-5 shows the workflow of methods when interacting with the user.

Figure 1-5 - Methods workflow

Methods workflow

Note When redisplaying the user input in the template, we run the risk of a XSS (Cross-Site Scripting) attack. You can find further information on how to prevent the XSS risk by implementing an escaping strategy in the Inside the View Layer chapter of "The Definitive Guide to symfony" book.

After you submit the form you should now see the page from Figure 1-6.

Figure 1-6 - Page displayed after submitting the form

Page displayed after submitting the form

Instead of creating the params array, it would be easier to get the information from the user directly in an array. Listing 1-6 modifies the name HTML attribute from widgets to store the field values in the contact array.

Listing 1-6 - Modification of the name HTML attribute from widgets

[php]
class ContactForm extends sfForm
{
  public function configure()
  {
    $this->setWidgets(array(
      'name'    => new sfWidgetFormInput(),
      'email'   => new sfWidgetFormInput(),
      'message' => new sfWidgetFormTextarea(),
    ));

    $this->widgetSchema->setNameFormat('contact[%s]');
  }
}

Calling setNameFormat() allows us to modify the name HTML attribute for all widgets. %s will automatically be replaced by the name of the field when generating the form. For example, the name attribute will then be contact[email] for the email field. PHP automatically creates an array with the values of a request including a contact[email] format. This way the field values will be available in the contact array.

We can now directly get the contact array from the request object as shown in Listing 1-7.

Listing 1-7 - New format of the name attributes in the action widgets

[php]
public function executeSubmit($request)
{
  $this->forward404Unless($request->isMethod('post'));

  $this->redirect('contact/thankyou?'.http_build_query($request->getParameter('contact')));
}

When displaying the HTML source of the form, you can see that symfony has generated a name attribute depending not only on the field name and format, but also an id attribute. The id attribute is automatically created from the name attribute by replacing the forbidden characters by underscores (_):

| Name | Attribute name | Attribute id | | --------- | -------------------- | ------------------- | | name | contact[name] | contact_name | | email | contact[email] | contact_email | | message | contact[message] | contact_message |

Another solution

In this example, we used two actions to manage the form: index for the display, submit for the submit. Since the form is displayed with the GET method and submitted with the POST method, we can also merge the two methods in the index method as shown in Listing 1-8.

Listing 1-8 - Merging of the two actions used in the form

[php]
class contactActions extends sfActions
{
  public function executeIndex($request)
  {
    $this->form = new ContactForm();

    if ($request->isMethod('post'))
    {
      $this->redirect('contact/thankyou?'.http_build_query($request->getParameter('contact')));
    }
  }
}

You can change the form method in the indexSuccess.php template by changing the form method attribute:

[php]
<form action="<?php echo url_for('contact/index') ?>" method="POST">

As we will see later, we prefer to use this syntax since it is shorter and makes the code more coherent and understandable.

Configuring the Widgets

Widgets options

If a website is managed by several webmasters, we would certainly like to add a drop-down list with themes in order to redirect the message according to what is asked (Figure 1-7). Listing 1-9 adds a subject with a drop-down list using the sfWidgetFormSelect widget.

Figure 1-7 - Adding a subject Field to the Form

![Adding a subject Field to the Form](/images/forms_book/en/01_07.png "Adding a subject Field to the Form")

Listing 1-9 - Adding a subject Field to the Form

[php]
class ContactForm extends sfForm
{
  protected static $subjects = array('Subject A', 'Subject B', 'Subject C');

  public function configure()
  {
    $this->setWidgets(array(
      'name'    => new sfWidgetFormInput(),
      'email'   => new sfWidgetFormInput(),
      'subject' => new sfWidgetFormSelect(array('choices' => self::$subjects)),
      'message' => new sfWidgetFormTextarea(),
    ));

    $this->widgetSchema->setNameFormat('contact[%s]');
  }
}

SIDEBAR The choices option of the sfWidgetFormSelect Widget

PHP does not make any distinction between an array and an associative array, so the array we used for the subject list is identical to the following code:

[php]
$subjects = array(0 => 'Subject A', 1 => 'Subject B', 2 => 'Subject C');

The generated widget takes the array key as the value attribute of the option tag, and the related value as content of the tag:

[php]
<select name="contact[subject]" id="contact_subject">
  <option value="0">Subject A</option>
  <option value="1">Subject B</option>
  <option value="2">Subject C</option>
</select>

In order to change the value attributes, we just have to define the array keys:

[php]
$subjects = array('A' => 'Subject A', 'B' => 'Subject B', 'C' => 'Subject C');

Which generates the HTML template:

[php]
<select name="contact[subject]" id="contact_subject">
  <option value="A">Subject A</option>
  <option value="B">Subject B</option>
  <option value="C">Subject C</option>
</select>

The `sfWidgetFormSelect' widget, like all widgets, takes a list of options as the first argument. An option may be mandatory or optional. The sfWidgetFormSelect widget has a mandatory option, choices. Here are the available options for the widgets we already used:

| Widget | Mandatory Options | Additional Options | | ---------------------- | --------------------- | -------------------------------- | | sfWidgetFormInput | - | type (default to text) | | | | is_hidden (default to false) | | sfWidgetFormSelect | choices | multiple (default to false) | | sfWidgetFormTextarea | - | - |

Tip If you want to know all of the options for a widget, you can refer to the complete API documentation available online at (http://www.symfony-project.org/api/1_1/). All of the options are explained, as well as the additional options default values. For instance, all of the options for the sfWidgetFormSelect are available here: (http://www.symfony-project.org/api/1_1/sfWidgetFormSelect).

The Widgets HTML Attributes

Each widget also takes a list of HTML attributes as second optional argument. This is very helpful to define default HTML attributes for the generated form tag. Listing 1-10 shows how to add a class attribute to the email field.

Listing 1-10 - Defining Attributes for a Widget

[php]
$emailWidget = new sfWidgetFormInput(array(), array('class' => 'email'));

// Generated HTML
<input type="text" name="contact[email]" class="email" id="contact_email" />

HTML attributes also allow us to override the automatically generated identifier, as shown in Listing 1-11.

Listing 1-11 - Overriding the id Attribute

[php]
$emailWidget = new sfWidgetFormInput(array(), array('class' => 'email', 'id' => 'email'));

// Generated HTML
<input type="text" name="contact[email]" class="email" id="email" />

It is even possible to set default values to the fields using the value attribute as Listing 1-12 shows.

Listing 1-12 - Widgets Default Values via HTML Attributes

[php]
$emailWidget = new sfWidgetFormInput(array(), array('value' => 'Your Email Here'));

// Generated HTML
<input type="text" name="contact[email]" value="Your Email Here" id="contact_email" />

This option works for input widgets, but is hard to carry through with checkbox or radio widgets, and even impossible with a textarea widget. The sfForm class offers specific methods to define default values for each field in a uniform way for any type of widget.

Note We recommend to define HTML attributes inside the template and not in the form itself (even if it is possible) to preserve the layers of separation as we will see in Chapter three.

Defining Default Values For Fields

It is often useful to define a default value for each field. For instance, when we display a help message in the field that disappears when the user focuses on the field. Listing 1-13 shows how to define default values via the setDefault() and setDefaults() methods.

Listing 1-13 - Default Values of the Widgets via the setDefault() and setDefaults() Methods

[php]
class ContactForm extends sfForm
{
  public function configure()
  {
    // ...

    $this->setDefault('email', 'Your Email Here');

    $this->setDefaults(array('email' => 'Your Email Here', 'name' => 'Your Name Here'));
  }
}

The setDefault() and setDefaults() methods are very helpful to define identical default values for every instance of the same form class. If we want to modify an existing object using a form, the default values will depend on the instance, therefore they must be dynamic. Listing 1-14 shows the sfForm constructor has a first argument that set default values dynamically.

Listing 1-14 - Default Values of the Widgets via the Constructor of sfForm

[php]
public function executeIndex($request)
{
  $this->form = new ContactForm(array('email' => 'Your Email Here', 'name' => 'Your Name Here'));

  // ...
}

SIDEBAR Protection XSS (Cross-Site Scripting)

When setting HTML attributes for widgets, or defining default values, the sfForm class automatically protects these values against XSS attacks during the generation of the HTML code. This protection does not depend on the escaping_strategy configuration of the settings.yml file. If a content has already been protected by another method, the protection will not be applied again.

It also protects the ' and " characters that might invalidate the generated HTML.

Here is an example of this protection:

[php]
$emailWidget = new sfWidgetFormInput(array(), array(
  'value' => 'Hello "World!"',
  'class' => '<script>alert("foo")</script>',
));
<br />
// Generated HTML
<input
  value="Hello &quot;World!&quot;"
  class="&lt;script&gt;alert(&quot;foo&quot;)&lt;/script&gt;"
  type="text" name="contact[email]" id="contact_email"
/>

Fordította: Sulik Szabolcs