Chương 1 – Tạo Form (symfony 1.2 legacy version)

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.

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

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.

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

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.

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

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.

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

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

/>