Caution: You are browsing the legacy symfony 1.x part of this website.

Chương 1 - Tạo Form

Form bao gồm các field như hidden input, text input, select box, và checkbox. Chương này giới thiệu về cách tạo form và quản lý các field sử dụng symfony forms framework.

Bạn cần symfony 1.1 trở lên để làm theo hướng dẫn này. Bạn cần tạo một project với application frontend.

Trước khi bắt đầu

Chúng ta sẽ tạo một form liên hệ để người dùng điền thông điệp muốn gửi.

Hình 1-1 - Form liên hệ

Contact form

Chúng ta sẽ tạo 3 field cho form này: name, email, và message người dùng muốn gửi. Nội dung người dùng nhập vào sẽ được hiển thị ở trang Thank you.

Hình 1-2 - Trang Thank you

Thank you page

Hình 1-3 - Tương tác giữa ứng dụng và người dùng

Interaction with the user schema

Widget

Lớp sfFormsfWidget

Trong symfony, form là đối tượng thừa kế từ lớp sfForm. Ở ví dụ này, chúng ta sẽ tạo lớp ContactForm thừa kế từ lớp sfForm.

note

sfForm là lớp cơ sở của tất cả các form, giúp dễ dàng quản lý cấu hình và vòng đời của form.

Bạn có thể bắt đầu cấu hình form bằng các thêm các widget sử dụng phương thức configure().

Mỗi widget ứng với một field của form. Trong ví dụ này, chúng ta cần thêm 3 widget mô tả 3 field: name, email, và message.

Listing 1-1 - Lớp ContactForm với 3 field

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

note

Trong hướng dẫn này, chúng tôi không hiển thị thẻ <?php ở code do mã nguồn ở đây luôn là PHP. Bạn cần thêm thẻ này khi tạo file PHP.

Các widget được xác định trong phương thức configure(). Phương thức này được gọi tự động bởi phương thức khởi tạo của lớp sfForm.

Phương thức setWidgets() dùng để xác định các widget dùng trong form. Phương thức setWidgets() nhận một mảng với key là tên field và value là widget object. Mỗi widget là một đối tượng thừa kế từ lớp sfWidget. Trong ví dụ này chúng ta sử dụng 2 widget:

  • sfWidgetFormInput: mô tả field input
  • sfWidgetFormTextarea: mô tả field textarea

note

Mặc định, chúng ta lưu các form class trong thư mục lib/form/. Bạn có thể chứa nó ở bất kì thư mục nào được quản lý bởi symfony autoloading mechanism, nhưng như chúng ta sẽ thấy sau này, symfony sử dụng thư mục lib/form/ để tạo form từ các model object.

Hiển thị Form

Form của chúng ta bây giờ đã sẵn sàng để sử dụng. Ta tạo một module để hiển thị form:

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

Trong module contact, sửa lại action index để cung cấp instance của form cho template.

Listing 1-2 - Class Actions của module contact

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

Khi tạo một form, phương thức configure() sẽ tự động được gọi.

Chúng ta cũng cần có một template để hiển thị form.

Listing 1-3 - Template hiển thị form

// 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>

Symfony form chỉ quản lý các widget hiển thị thông tin tới người dùng. Trong template indexSuccess, dòng <?php echo $form ?> chỉ hiển thị 3 field. Các thành phần khác như thẻ HTML form và nút submit, lập trình viên phải tự thêm vào. Ban đầu nó không được tự nhiên lắm, nhưng sau này chúng ta sẽ thấy lợi ích và sự tiện lợi của các form nhúng này.

Sử dụng <?php echo $form ?> rất hữu ích khi tạo prototype. Nó cho phép lập trình viên tập trung vào business logic mà không cần quan tâm đến việc trình bày. Chương 3 sẽ mô tả cách cá nhân hóa template và form layout.

note

Khi hiển thị một object sử dụng <?php echo $form ?>, PHP engine sẽ tự động hiển thị nội dung text của đối tượng $form. Để chuyển một đối tượng sang dạng chuỗi, PHP cố gắng thực thi magic method __toString(). Mỗi widget đều implement magic method này để chuyển đối tượng thành mã HTML. Việc gọi <?php echo $form ?> tương đương với <?php echo $form->__toString() ?>.

Bây giờ chúng ta có thể thấy form từ trình duyệt (Hình 1-4) bằng cách gõ địa chỉ đến action contact/index (/frontend_dev.php/contact).

Hình 1-4 - Form liên hệ được tạo ra

Generated Contact Form

Listing 1-4 Code HTML được tạo ra ở template.

<form action="/frontend_dev.php/contact/submit" method="POST">
  <table>
 
    <!-- Beginning of generated code by <?php echo $form ?>
 -->
    <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>
    <!-- End of generated code by <?php echo $form ?>
 -->
 
    <tr>
      <td colspan="2">
        <input type="submit" />
      </td>
    </tr>
  </table>
</form>

Ta có thể thấy rằng form được hiển thị với 3 dòng <tr> trong bảng. Đó là lý do tại sao chúng ta thêm thẻ đóng </table>. Mỗi dòng có một thẻ <label> và form tag (<input> hoặc <textarea>).

Label

Label của mỗi field được tự động tạo ra. Mặc định, label được chuyển từ field name theo 2 nguyên tắc: kí tự đầu được viết hoa và dấu gạch dưới được thay thế bởi dấu cách. Ví dụ:

$this->setWidgets(array(
  'first_name' => new sfWidgetFormInput(), // generated label: "First name"
  'last_name'  => new sfWidgetFormInput(), // generated label: "Last name"
));

Mặc dù việc tự động tạo label rất tiện lợi, nhưng framework cũng cho phép bạn tự tạo label với phương thức setLabels():

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

Bạn có thể chỉnh sửa từng label với phương thức setLabel():

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

Trong chương 3, bạn sẽ thấy rằng có thể extend label từ template để customize form.

sidebar

Widget Schema

Khi chúng ta sử dụng phương thức setWidgets(), symfony tạo đối tượng sfWidgetFormSchema. Đối tượng này là một widget cho phép bạn mô tả tập các widget. Trong form ContactForm, chúng ta gọi phương thức setWidgets(). Nó tương đương với đoạn code sau:

$this->setWidgetSchema(new sfWidgetFormSchema(array(
  'name'    => new sfWidgetFormInput(),
  'email'   => new sfWidgetFormInput(),
  'message' => new sfWidgetFormTextarea(),
)));
 
// almost equivalent to :
 
$this->widgetSchema = new sfWidgetFormSchema(array(
  'name'    => new sfWidgetFormInput(),
  'email'   => new sfWidgetFormInput(),
  'message' => new sfWidgetFormTextarea(),
));

Bạn sẽ thấy trong Chương 5, khái niệm "schema widget" giúp việc quản lý các form nhúng trở nên dễ dàng.

Beyond generated tables

Mặc định form được hiển thị dưới dạng bảng, nhưng ta có thể đổi format layout khác. Các format khác nhau được xác định trong lớp thừa kế từ sfWidgetFormSchemaFormatter. Mặc định, một form sử dụng format table xác định trong lớp sfWidgetFormSchemaFormatterTable. Bạn cũng có thể sử dụng format list:

class ContactForm extends sfForm
{
  public function configure()
  {
    $this->setWidgets(array(
      'name'    => new sfWidgetFormInput(),
      'email'   => new sfWidgetFormInput(),
      'message' => new sfWidgetFormTextarea(),
    ));
 
    $this->widgetSchema->setFormFormatterName('list');
  }
}

2 format này là có sẵn và trong Chương 5 bạn sẽ biết cách tự tạo lớp format riêng. Chúng ta đã biết về cách hiển thị form, hãy chuyển qua phần quản lý dữ liệu submit.

Submit Form

Khi tạo template để hiển thị form, chúng ta sử dụng đường dẫn contact/submit trong thẻ form. Ta cần thêm action submit trong module contact lấy nội dung người dùng nhập vào và chuyển sang trang thank you để hiển thị thông tin đã đưa lên.

Listing 1-5 - Action submit trong module contact

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 là một function có sẵn của PHP, tạo ra chuỗi URL-encoded từ mảng các tham số.

Phương thức executeSubmit() thực hiện 3 hành động:

  • Kiểm tra xem nội dung được submit có sử dụng HTTP method POST không. Nếu không phải POST method, người dùng sẽ được chuyển sang trang 404. Trong template indexSuccess, chúng ta đã khai báo submit method là POST (<form ... method="POST">):

    $this->forward404Unless($request->isMethod('post'));
  • Tiếp theo chúng ta lấy dữ liệu người dùng nhập vào và chứa chúng trong mảng params:

    $params = array(
      'name'    => $request->getParameter('name'),
      'email'   => $request->getParameter('email'),
      'message' => $request->getParameter('message'),
    );
  • Cuối dùng, chuyển người dùng sang trang Thank you (contact/thankyou) để hiển thị thông tin:

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

Thay vì chuyển người dùng sang trang khác, chúng ta cũng có thể tạo template submitSuccess.php để hiển thị nội dung, Nhưng tốt nhất là nên chuyển người dùng sang trang khác sau khi thực hiện một yêu cầu với POST method:

  • Việc này tránh form bị submit lại khi người dùng reload trang.

  • Người dùng có thể click vào nút quay lại trang trước mà không hiển thị pop-up yêu cầu submit lại form.

tip

Bạn có thể chú ý rằng phương thức executeSubmit() có điểm khác phương thức executeIndex(). Khi gọi những phương thức này, symfony cung cấp đối tượng sfRequest làm tham số. Với PHP, bạn không bắt buộc phải thu thập tất cả các tham số, do đó chúng ta không xác định biến request trong phương thức executeIndex(), do nó không cần thiết.

Hình 1-5 chỉ ra workflow của các phương thức tương tác với người dùng.

Figure 1-5 - Methods workflow

Methods workflow

note

Khi hiển thị nội dung người dùng đã nhập vào, chúng ta có thể gặp nguy hiểm từ XSS (Cross-Site Scripting) attack. Bạn có thể tìm hiểu về các tránh XSS bằng cách thực thi một escaping strategy ở chương Inside the View Layer trong cuốn "The Definitive Guide to symfony".

Sau khi submit form dữ liệu được hiển thị như hình 1-6.

Figure 1-6 - Trang hiển thị sau khi submit form

Page displayed after submitting the form

Thay vì tạo mảng params, có một cách dễ dàng hơn để lấy thông tin của người dùng vào một mảng. Đó là sửa lại HTML attribute name ở widget để chứa giá trị của field vào mảng contact.

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

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

setNameFormat() cho phép bạn sửa HTML attribute name cho tất cả các widget. %s sẽ được tự động thay thế bởi tên của field khi tạo form. Ví dụ, attribute name của field email sẽ trở thành contact[email]. PHP tự động tạo một mảng từ nội dung user đưa lên.

Bây giờ chúng ta có thể lấy trực tiếp mảng contact từ đối tượng request.

Listing 1-7 - format mới của attribute name trong action widget

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

Khi xem HTML source của form, bạn có thể thấy symfony đã tạo attribute không chỉ cho field name và format, mà còn cho attribute id. Attribute id được tạo tự động từ attribute name bằng cách thay thế bởi kí tự gạch dưới (_):

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

Giải pháp khác

Trong ví dụ này, chúng ta đã sử dụng 2 action để quản lý form: index để hiển thị, submit để submit. Do form được hiển thị với method GET và submit với method POST, chúng ta có thể gộp 2 method này vào method index.

Listing 1-8 - Gộp 2 action dùng trong form

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')));
    }
  }
}

Chúng ta cũng cần thay đổi attribute action của thẻ form trong template indexSuccess.php:

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

Như sẽ thấy sau này, chúng ta sẽ thường xuyên dùng cấu trúc này do nó ngắn hơn và code mạch lạc, dễ hiểu hơn.

Cấu hình Widget

Widgets options

Nếu website được quản lý bởi nhiều webmaster, chúng ta muốn thêm một drop-down list để chuyển message cho đúng người liên quan (Hình 1-7). Ta thêm subject drop-down list sử dụng widget sfWidgetFormSelect.

Figure 1-7 - Thêm field subject vào Form

Adding a <code>subject</code> Field to the Form

Listing 1-9 - Thêm field subject vào Form

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

choices option của Widget sfWidgetFormSelect

PHP không có sự phân biệt giữa 1 mảng và một mảng kết hợp, do đó ở đây chúng ta dùng mảng để xác định subject list:

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

widget được tạo ra sẽ nhận các key của mảng làm attribute value của thẻ option, và value tương ứng làm nội dung của thẻ:

<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>

Chúng ta có thể thay đổi attribute value, bằng cách xác định trong mảng:

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

Code HTML tạo ra trong template:

<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>

Widget sfWidgetFormSelect, giống như các widget khác, nhận danh sách các option làm tham số đầu tiên. Một option có thể là bắt buộc hoặc tùy chọn. Widget sfWidgetFormSelect có một option bắt buộc, choices. Dưới đây là các option của widget chúng ta đã sử dụng:

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

tip

Nếu bạn muốn biết về tất cả các option của một widget, bạn có thể tham khảo API đầy đủ tại (/api/1_2/). Tất cả các option đều được mô tả, kèm với các giá trị mặc định. Ví dụ, tất cả các option của sfWidgetFormSelect có ở đây: (/api/1_2/sfWidgetFormSelect).

Widget HTML Attribute

Mỗi widget cũng nhận một danh sách các HTML attributes làm tham số thứ 2. Listing 1-10 chỉ ra cách thêm attribute class vào field email.

Listing 1-10 - Xác định Attribute cho một Widget

$emailWidget = new sfWidgetFormInput(array(), array('class' => 'email'));
 
// Generated HTML
<input type="text" name="contact[email]" class="email" id="contact_email" />

HTML attribute cũng cho phép chúng ta override những thứ đã tự động tạo sẵn.

Listing 1-11 - Override id Attribute

$emailWidget = new sfWidgetFormInput(array(), array('class' => 'email', 'id' => 'email'));
 
// Generated HTML
<input type="text" name="contact[email]" class="email" id="email" />

Ta còn có thể thiết lập giá trị mặc định cho field sử dụng attribute value.

Listing 1-12 - Widgets Default Values via HTML Attributes

$emailWidget = new sfWidgetFormInput(array(), array('value' => 'Your Email Here'));
 
// Generated HTML
<input type="text" name="contact[email]" value="Your Email Here" id="contact_email" />

Option này sử dụng cho widget input, nhưng cũng có thể áp dụng cho widget checkbox, radiotextarea.

note

Chúng tôi khuyên bạn xác định HTML attributes trong template và không xác định trong form (mặc dù có thể thực hiện được) để đảm bảo sự tách biệt giữa các tầng như mô tả trong Chương 3.

Xác định giá trị mặc định cho các Field

Xác định giá trị mặc định cho mỗi field khá hữu ích. Ví dụ, chúng ta muốn hiển thị một thông điệp help trong field và sẽ biến mất khi user click vào field. Listing 1-13 chỉ ra cách xác định giá trị mặc định dựa trên method setDefault()setDefaults().

Listing 1-13 - Thiết lập giá trị mặc định dựa trên phương thức setDefault()setDefaults()

class ContactForm extends sfForm
{
  public function configure()
  {
    // ...
 
    $this->setDefault('email', 'Your Email Here');
 
    $this->setDefaults(array('email' => 'Your Email Here', 'name' => 'Your Name Here'));
  }
}

Listing 1-14 chỉ ra cách thiết lập giá trị mặc định từ hàm khởi tạo của sfForm.

Listing 1-14 - Thiết lập giá trị mặc định của Widgets từ hàm khởi tạo của sfForm

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

sidebar

Protection XSS (Cross-Site Scripting)

Khi thiết lập HTML attributes hoặc giá trị mặc định cho các widget, lớp sfForm tự động escape các giá trị khỏi tấn công XSS trong khi tạo HTML code. Việc này không phụ thuộc vào cấu hình escaping_strategy trong file settings.yml. Nếu một nội dung đã được escape bởi phương thức khác, escape sẽ không lặp lại.

Nó cũng escape kí tự '" có thể không hợp lệ khi tạo HTML.

Dưới đây là một ví dụ:

$emailWidget = new sfWidgetFormInput(array(), array(
  'value' => 'Hello "World!"',
  'class' => '<script>alert("foo")</script>',
));
 
// 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"
/>