Belajar Symfony - Forms & Validation
Episode 7 of 27

Belajar Symfony - Forms & Validation

Membangun FormType dengan data binding ke entity, memahami proteksi CSRF otomatis, constraint validation berbasis attribute, custom validator dan validation groups, serta theming form di Twig dengan form_start, form_row, dan form_end.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Pada episode 6, kita menulis artikel lewat $request->request->get('title') secara manual. Episode 7 memperbaiki pendekatan itu dengan Form component: FormType yang memetakan input HTTP ke entity secara otomatis, plus lapisan validation yang menjamin data yang masuk ke database selalu bersih.

Mengapa penting? Dua masalah besar dalam aplikasi nyata: (1) data tidak valid masuk ke database (judul kosong, email format salah), dan (2) CSRF — formulir bisa disalahgunakan dari situs lain. Form + Validation Symfony menyelesaikan keduanya secara bawaan, dan ini pola yang akan kalian temui di hampir semua project Symfony produksi.

Membuat FormType

Buat form untuk Article
php bin/console make:form ArticleFormType

FormType mendefinisikan field dan binding-nya ke entity:

src/Form/ArticleFormType.php
<?php
 
namespace App\Form;
 
use App\Entity\Article;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
 
class ArticleFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title', TextType::class)
            ->add('body', TextareaType::class)
            ->add('save', SubmitType::class, ['label' => 'Simpan']);
    }
 
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Article::class,
            'csrf_protection' => true,
        ]);
    }
}

data_class mengikat form ke entity Article; csrf_protection (default aktif) otomatis menyuntikkan token CSRF. Nama method harus sesuai getter/setter entity: titlegetTitle()/setTitle().

Memproses Form di Controller

Pola standar handleRequest:

Proses form di controller
use App\Form\ArticleFormType;
 
#[Route('/articles/new', name: 'app_article_new')]
public function new(Request $request, EntityManagerInterface $em): Response
{
    $article = new Article();
    $form = $this->createForm(ArticleFormType::class, $article);
    $form->handleRequest($request);
 
    if ($form->isSubmitted() && $form->isValid()) {
        $em->persist($article);
        $em->flush();
 
        return $this->redirectToRoute('app_article_show', ['id' => $article->getId()]);
    }
 
    return $this->render('article/new.html.twig', [
        'form' => $form->createView(),
    ]);
}

Mengapa pola ini penting? Karena handleRequest mengikat data request ke objek $article secara otomatis dan menjalankan validasi sebelum isValid() mengembalikan nilai. Perhatikan pola PRG (Post/Redirect/Get): setelah simpan, redirect — bukan render langsung — agar refresh halaman tidak mengirim ulang POST.

Constraint Validation

Tanpa constraint, isValid() selalu benar. Tambahkan aturan validasi pada property entity:

Constraint pada entity Article
use Symfony\Component\Validator\Constraints as Assert;
 
#[ORM\Column(length: 255)]
#[Assert\NotBlank(message: 'Judul tidak boleh kosong.')]
#[Assert\Length(min: 3, max: 255)]
private ?string $title = null;
 
#[ORM\Column(type: 'text')]
#[Assert\NotBlank]
#[Assert\Length(min: 10, minMessage: 'Konten minimal 10 karakter.')]
private ?string $body = null;

Constraint bawaan yang paling sering dipakai:

ConstraintFungsi
NotBlankNilai tidak boleh kosong
LengthPanjang minimal/maksimal
EmailFormat email valid
RegexPola tertentu (mis. hanya alfanumerik)
UniqueEntityTidak boleh duplikat di database (perlu import)
ChoiceHanya nilai dari daftar

Keindahan sistem ini: constraint berlaku di mana pun validasi dijalankan — form, API Platform (episode 13), atau validasi manual lewat ValidatorInterface.

Custom Validator dan Groups

Ketika constraint bawaan tidak cukup, buat constraint sendiri:

Buat custom constraint
php bin/console make:validator IsValidSlug

Hasilnya dua file: class IsValidSlug (constraint) dan IsValidSlugValidator (logika). Constraint dipakai seperti bawaan:

Memakai custom constraint
use App\Validator as AppAssert;
 
#[AppAssert\IsValidSlug]
private ?string $slug = null;

Validation groups memungkinkan aturan berbeda tergantung konteks — misalnya kolom wajib saat create tapi opsional saat update:

Validation group
#[Assert\NotBlank(groups: ['create'])]
private ?string $title = null;

Lalu validasi dijalankan dengan grup tertentu: $validator->validate($article, groups: ['create']). Form bisa menetapkan group via opsi validation_groups.

Theming Form di Twig

Render form di template:

templates/article/new.html.twig
{% extends 'base.html.twig' %}
 
{% block body %}
    <h1>Tulis Artikel Baru</h1>
 
    {{ form_start(form) }}
        {{ form_errors(form) }}
 
        {{ form_row(form.title) }}
        {{ form_row(form.body) }}
        {{ form_row(form.save) }}
    {{ form_end(form) }}
{% endblock %}
FunctionFungsi
form_startTag <form> + token CSRF
form_endTag </form> + field yang belum dirender
form_rowLabel + widget + error sekaligus
form_widgetHanya input field
form_errorsError validasi

Kesalahan umum: lupa memanggil form_end sehingga token CSRF tidak ter-render — hasilnya form akan gagal validasi CSRF saat submit.

Common Pitfalls

  • Validasi tidak jalan: pastikan use Symfony\Component\Validator\Constraints as Assert; dan constraint berada di property (bukan getter) — atau pakai attribute #[Assert\Callback] untuk validasi kompleks.
  • Data tidak ter-submit: nama field di buildForm harus sama dengan properti entity, dan form harus isSubmitted() dan isValid() sebelum diproses.
  • Mengabaikan validation_groups: dua form memakai entity yang sama dengan aturan berbeda — ini kasus grup paling umum.

Tip

Selalu simpan data setelah isValid(), jangan pernah sebelum. Kebiasaan ini mencegah data tidak valid masuk database dan membuat error form lebih mudah ditelusuri karena error terikat ke field-nya masing-masing.

Penutup

Pada episode 7 ini, kalian telah membangun input yang aman dan tervalidasi.

Inti yang harus dibawa pulang:

  • FormType memetakan request ke entity; data_class adalah pengikatnya.
  • Pola handleRequest + isSubmitted() + isValid() adalah standar emas.
  • CSRF protection aktif bawaan — jangan pernah mematikannya tanpa alasan.
  • Constraint ditulis sebagai attribute di entity; berlaku di form, API, dan validasi manual.
  • Custom validator untuk logika unik; validation groups untuk konteks berbeda.
  • Twig: form_row untuk keseluruhan, form_widget untuk field saja, jangan lupa form_end.

Di episode 8 selanjutnya kita mengupas jantung Symfony: Dependency Injection & Services — service container, autowiring, konfigurasi services.yaml, binding interface ke implementasi, parameter, dan lazy loading. Sampai jumpa di episode 8!