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ệ
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
Hình 1-3 – Tương tác giữa ứng dụng và người dùng
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
ContactFormextends
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ả fieldinput
sfWidgetFormTextarea
: mô tả fieldtextarea
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
contactActionsextends
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
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
ContactFormextends
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ảiPOST
method, người dùng sẽ được chuyển sang trang 404. Trong templateindexSuccess
, 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
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
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
ContactFormextends
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
contactActionsextends
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
Listing 1-9 – Thêm field subject
vào Form
class
ContactFormextends
sfForm{
protectedstatic
$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
, radio
và textarea
.
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()
và setDefaults()
.
Listing 1-13 – Thiết lập giá trị mặc định dựa trên phương thức setDefault()
và setDefaults()
class
ContactFormextends
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ự '
và "
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 "World!""
class
="<script>alert("foo")</script>"
type="text"
name="contact[email]"
id="contact_email"
/>