สร้างคลาส IssueToNumberTransformer ไว้สำหรับทำหน้าที่ในการแปลงข้อมูล issue number ไปเป็น Issue object และแปลงกลับ
// src/AppBundle/Form/DataTransformer/IssueToNumberTransformer.php
namespace AppBundle\Form\DataTransformer;
use AppBundle\Entity\Issue;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class IssueToNumberTransformer implements DataTransformerInterface
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* Transforms an object (issue) to a string (number).
*
* @param Issue|null $issue
* @return string
*/
public function transform($issue)
{
if (null === $issue) {
return '';
}
return $issue->getId();
}
/**
* Transforms a string (number) to an object (issue).
*
* @param string $issueNumber
* @return Issue|null
* @throws TransformationFailedException if object (issue) is not found.
*/
public function reverseTransform($issueNumber)
{
// no issue number? It's optional, so that's ok
if (!$issueNumber) {
return;
}
$issue = $this->em
->getRepository(Issue::class)
// query for the issue with this id
->find($issueNumber)
;
if (null === $issue) {
// causes a validation error
// this message is not shown to the user
// see the invalid_message option
throw new TransformationFailedException(sprintf(
'An issue with number "%s" does not exist!',
$issueNumber
));
}
return $issue;
}
}
เสร็จสิ้น ตอนนี้ user สามารถใส่ issue number ไปใน text field ได้แล้วและก็สามารถแปลงข้อมูลกลับไปเป็น Issue object ได้เช่นกัน นั่นหมายความว่า หากการ submit ข้อมูลจากฟอร์มสำเร็จ Form component จะส่งผ่าน Issue object จริง ๆ ไปยัง Task::setIssue() แทนการส่ง issue number ไป
ถ้าหาก Issue นั้นไม่มีอยู่ form error จะถูกสร้างขึ้นและแสดงข้อความ error message ที่เราได้ตั้งไว้ผ่าน invalid_message field option
ข้อควรระวังในการใช้ transfomers จากตัวอย่างข้างล่างเป็นตัวอย่างที่ผิด เพราะตัว transformer จะถูกนำไปใช้กับฟอร์มทั้งหมด แทนที่จะใช้เพียงแค่ field ใด field หนึ่ง
Creating a Reusable issue_selector Field
จากตัวอย่างข้างบน คุณได้ใช้ transformer กับ text field ธรรมดา แต่ถ้าหากคุณมีการใช้ transformation มากขึ้น มันจะดีกว่าไหมที่สร้าง custom field type ให้ทำงานอัตโนมัติ
เริ่มแรก สร้าง custom field type ขึ้นมา
จากตัวอย่างข้างบนจะแสดงผลเป็น text field (getParent()) และ data transformer จะทำงานอัตโนมัติ อีกทั้งการกำหนดค่า invalid_message option ก็ทำได้สะดวกยิ่งขึ้น
Model data - นี่คือข้อมูลในรูปแบบที่ใช้ใน application ของคุณ (เช่น Issue object) ถ้าคุณเรียกใช้ Form::getData() หรือ Form::setData() แสดงว่าคุณกำลังทำงานอยู่กับ "model" data
Norm Data - นี่คือ normalized version ของข้อมูลคุณโดยทั่วไปแล้วจะเหมือนกับ "model" data ของคุณ ไม่ได้ใช้กันทั่วไป
View Data - นี่คือรูปแบบที่ใช้ในการกรอกข้อมูลในฟอร์ม รวมไปถึงข้อมูลที่ user submit มา โดยเมื่อคุณเรียกใช้ Form::submit($data) ตัวแปร $data นี้อยู่ในรูปแบบ "view" data
ในตัวอย่างนี้ text field คาดหวังรูปแบบธรรมดา คือในรูปแบบ "norm" และ "view" ด้วยเหตุนี้ประเภทของ transformer ที่ควรจะเป็นคือ "model" transformer (ซึ่งแปลง to/from รูปแบบ norm - string issue number - ไปเป็นรูปแบบ model - Issue object)
ความแตกต่างระหว่างประเภทของ transformer เป็นเรื่องที่ละเอียดอ่อน โดยคุณควรคำนึงเสมอว่า "norm" data สำหรับ field นั้น ๆ ควรจะเป็นยังไง ตัวอย่างเช่น "norm" data สำหรับ text field คือ string แต่สำหรับ DateTime object ควรเป็น date field
How to Use Data Transformers
Data transformers ใช้เพื่อแปลงข้อมูลของ field ไปสู่รูปแบบที่ต้องการเพื่อนำแสดงไปยังฟอร์ม (และส่งข้อมูลกลับมาหลังจาก submit) ยกตัวอย่างเช่น
DateType
field แสดงผลเป็นรูปแบบyyyy-MM-dd
ผ่าน input textbox โดย data transformers สามารถแปลงค่าyyyy-MM-dd
ของ DateTime field ไปเป็นรูปแบบ string เพื่อแสดงผลผ่านฟอร์ม และแปลงมันกลับไปเป็นDateTime
object เมื่อ submitSimple Example: Transforming String Tags from User Input to an Array
สมมติคุณมีฟอร์มดังภาพ
โดยภายใน
tags
เก็บข้อมูลเป็นอาร์เรย์ แต่ตอนแสดงให้ user ใช้งานจะแสดงผลเป็น string โดยให้ใส่ , คั่นไว้เพื่อความสะดวกในการใช้งานของ userวิธีที่การใช้ data transformer เพื่อจัดการข้อมูลของ
tags
field ทำได้โดยใช้คลาสCallbackTransformer
ฟังก์ชัน
CallbackTransformer
ต้องการอาร์กิวเมนต์ 2 ตัว ตัวแรกแปลงค่า original ไปเป็นรูปแบบที่จะใช้แสดงผล ตัวที่สองจะแปลงข้อมูลที่ submit มาไปเป็นรูปแบบที่ต้องการใช้งานต่อในโค้ดคุณสามารถเพิ่ม transformer ในขณะกำลังเพิ่ม field ได้ดัวยดังรูป
Harder Example: Transforming an Issue Number into an Issue Entity
สมมติคุณมีความสัมพันธ์ many-to-one ของ model
Task
ไปIssue
(แต่ละ Task จะมี FK ของ Issue) การเพิ่ม listbox เพื่อแสดง issue ทั้งหมดอาจใช้เวลาในการโหลดข้อมูลนานเกินไป ดังนั้นอีกวิธีการนึงคือ คุณเพิ่ม textbox เพื่อให้ user ใส่เลข issue แทน เริ่มการกำหนด text field ดังรูปจากรูปเมื่อ submit ข้อมูลมา property
issue
ของ model Task จะได้ข้อมูลมาเป็น string แล้วเราจะแปลงมันกลับไปเป็น objectIssue
ได้อย่างไรCreating the Transformer
คุณอาจใช้
CallbackTransformer
แบบตัวอย่างที่แล้วก็ได้ แต่ในตัวอย่างนี้จะซับซ้อนขึ้นมาหน่อยโดยใช้วิธีการสร้างคลาส transformer ขึ้นมาใหม่ โดยเอาไว้ใช้กับคลาสTaskType
สร้างคลาส
IssueToNumberTransformer
ไว้สำหรับทำหน้าที่ในการแปลงข้อมูล issue number ไปเป็นIssue
object และแปลงกลับคล้ายกับตัวอย่างแรก คือการแปลงข้อมูลมีสองทิศทางโดยเมธอด
tranform()
จะแปลงข้อมูลในโค้ดไปเป็นรูปแบบที่สามารถนำไปแสดงผลในฟอร์มได้ (เช่น แปลงจากIssue
object เป็นid
ในรูปแบบ string) ส่วนเมธอดreverseTransform()
จะแปลงข้อมูลที่มาจากการ submit ไปเป็นรูปแบบตามที่เราต้องการ (เช่น แปลงid
string กลับไปเป็นIssue
object)หากต้องการดูการเกิด validation error ให้ใช้
TransformationFailedException
แต่ข้อความที่คุณส่งผ่าน exception นี้จะไม่ปรากฏแก่ผู้ใช้ คุณสามารถตั้งค่าข้อความนั้นด้วยตัวเลือกinvalid_message
(ดูด้านล่าง)Using the Transformer
ขั้นต่อไป คุณจำเป็นต้องใช้
IssueToNumberTransformer
object ภายในTaskType
โดยเพิ่มไปยังissue
field ซึ่งทำได้โดยเพิ่มเมธอด__construct()
ดังตัวอย่างที่นี้สามารถใช้
TaskType
ได้ดังตัวอย่างเสร็จสิ้น ตอนนี้ user สามารถใส่ issue number ไปใน text field ได้แล้วและก็สามารถแปลงข้อมูลกลับไปเป็น Issue object ได้เช่นกัน นั่นหมายความว่า หากการ submit ข้อมูลจากฟอร์มสำเร็จ Form component จะส่งผ่าน
Issue
object จริง ๆ ไปยังTask::setIssue()
แทนการส่ง issue number ไปถ้าหาก Issue นั้นไม่มีอยู่ form error จะถูกสร้างขึ้นและแสดงข้อความ error message ที่เราได้ตั้งไว้ผ่าน
invalid_message
field optionCreating a Reusable issue_selector Field
จากตัวอย่างข้างบน คุณได้ใช้ transformer กับ
text
field ธรรมดา แต่ถ้าหากคุณมีการใช้ transformation มากขึ้น มันจะดีกว่าไหมที่สร้างcustom field type
ให้ทำงานอัตโนมัติเริ่มแรก สร้าง custom field type ขึ้นมา
จากตัวอย่างข้างบนจะแสดงผลเป็น text field (
getParent()
) และ data transformer จะทำงานอัตโนมัติ อีกทั้งการกำหนดค่าinvalid_message
option ก็ทำได้สะดวกยิ่งขึ้นโดยตราบใดที่คุณใช้
autowire
และautoconfigure
คุณสามารถเริ่มต้นใช้ฟอร์มได้ทันทีAbout Model and View Transformers
จากตัวอย่างข้างบน เป็นการ transformer ในลักษณะ "model" transformer ในความเป็นจริง transformers มีสองประเภทที่แตกต่างกันและมีข้อมูลพื้นฐานที่แตกต่างกันอีกสามประเภท
ในฟอร์มหนึ่ง ข้อมูลสามประเภทได้แก่
Issue
object) ถ้าคุณเรียกใช้Form::getData()
หรือForm::setData()
แสดงว่าคุณกำลังทำงานอยู่กับ "model" dataForm::submit($data)
ตัวแปร$data
นี้อยู่ในรูปแบบ "view" dataโดย transformers สองประเภทที่แตกต่างกันนี้ จะช่วยแปลงข้อมูลจากแต่ละประเภทข้อมูล
Model transformers:
transform()
: "model data" => "norm data"reverseTransform()
: "norm data" => "model data"View Transformer:
transform()
: "norm data" => "view data"reverseTransform()
: "view data" => "norm data"ซึ่งการใช้ transformer นั้นก็ขึ้นอยู่กับสถานการณ์ที่คุณเจอ
โดยการใช้ view transformer ให้เรียกใช้
addViewTransformer()
So why Use the Model Transformer?
ในตัวอย่างนี้ text field คาดหวังรูปแบบธรรมดา คือในรูปแบบ "norm" และ "view" ด้วยเหตุนี้ประเภทของ transformer ที่ควรจะเป็นคือ "model" transformer (ซึ่งแปลง to/from รูปแบบ norm - string issue number - ไปเป็นรูปแบบ model - Issue object)
ความแตกต่างระหว่างประเภทของ transformer เป็นเรื่องที่ละเอียดอ่อน โดยคุณควรคำนึงเสมอว่า "norm" data สำหรับ field นั้น ๆ ควรจะเป็นยังไง ตัวอย่างเช่น "norm" data สำหรับ![01](https://user-images.githubusercontent.com/30454532/31996309-3e96450e-b9b2-11e7-8773-075dc9b3b0eb.png)
text
field คือ string แต่สำหรับDateTime
object ควรเป็นdate
fieldSOURCE