Bağımsız Kullanılan Formlar

Nette Forms, web formlarının oluşturulmasını ve işlenmesini çarpıcı biçimde kolaylaştırır. Onları uygulamalarınızda, framework'ün geri kalanı olmadan tümüyle bağımsız kullanabilirsiniz; bu bölümde gösterildiği gibi.

Ancak Nette Application ve presenter'ları kullanıyorsanız, size ayrılmış bir kılavuz var: presenter'larda formlar.

İlk Form

Başlamadan önce paketi Composer ile kurun:

composer require nette/forms

Basit bir kayıt formu yazmayı deneyelim. Kodu şöyle olacak (tam kod):

use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Ad:');
$form->addPassword('password', 'Parola:');
$form->addSubmit('send', 'Kaydol');

Ve onu çok kolayca render edelim:

$form->render();

Tarayıcıdaki sonuç şöyle görünmeli:

Form, Nette\Forms\Form sınıfının bir nesnesidir (presenter'larda Nette\Application\UI\Form sınıfı kullanılır). Ona “name”, “password” adlı öğeleri ve bir gönder düğmesi ekledik.

Şimdi forma can verelim. $form->isSuccess() sorgusuyla, formun gönderilip gönderilmediğini ve geçerli biçimde doldurulup doldurulmadığını öğreniriz. Öyleyse veriyi çıktılayacağız. Form tanımından sonra şunu ekleyin:

if ($form->isSuccess()) {
	echo 'Form doldurulup başarıyla gönderildi';
	$data = $form->getValues();
	// $data->name adı içerir
	// $data->password parolayı içerir
	var_dump($data);
}

getValues() metodu, gönderilen veriyi bir ArrayHash nesnesi olarak döndürür. Bunun nasıl değiştirileceğini daha sonra göstereceğiz. $data nesnesi, kullanıcının girdiği verilerle birlikte name ve password anahtarlarını içerir.

Genellikle veriyi doğrudan ileri işleme, örneğin veritabanına eklemeye göndeririz. Ancak işleme sırasında bir hata oluşabilir, örneğin kullanıcı adı zaten alınmış olabilir. Bu durumda hatayı addError() ile forma geri aktarır ve hata mesajıyla birlikte yeniden render edilmesini sağlarız.

$form->addError('Üzgünüz, bu kullanıcı adı zaten alınmış.');

Formu işledikten sonra bir sonraki sayfaya yönlendiririz. Bu, yenile ya da geri düğmelerine tıklanarak veya tarayıcı geçmişinde gezinilerek formun istenmeden yeniden gönderilmesini önler.

Form varsayılan olarak POST metoduyla aynı sayfaya gönderilir. İkisi de değiştirilebilir:

$form->setAction('/submit.php');
$form->setMethod('GET');

Ve aslında hepsi bu :-) İşleyen ve kusursuz biçimde güvenli bir formumuz var.

Başka form öğeleri eklemeyi de deneyin.

Öğelere Erişim

Form ve tek tek öğeleri bileşen olarak adlandırılır. Kökü form olan bir bileşen ağacı oluştururlar. Tek tek form öğelerine şöyle erişebilirsiniz:

$input = $form->getComponent('name');
// alternatif söz dizimi: $input = $form['name'];

$button = $form->getComponent('send');
// alternatif söz dizimi: $button = $form['send'];

Öğeler unset ile kaldırılır:

unset($form['name']);

Doğrulama Kuralları

Geçerli sözcüğü geçti, ama formun henüz hiçbir doğrulama kuralı yok. Bunu düzeltelim.

Ad zorunlu olacak, bu yüzden onu setRequired() metoduyla işaretliyoruz. Argümanı, kullanıcı adı doldurmazsa görüntülenecek hata mesajının metnidir. Argüman verilmezse varsayılan hata mesajı kullanılır.

$form->addText('name', 'Ad:')
	->setRequired('Lütfen bir ad girin.');

Formu adı doldurmadan göndermeyi deneyin; bir hata mesajının çıktığını göreceksiniz. Tarayıcı ya da sunucu, siz alanı doldurana dek onu reddedecek.

Aynı zamanda, girdiye yalnızca boşluk yazarak sistemi kandıramazsınız. Mümkün değil. Nette, baştaki ve sondaki boşlukları otomatik olarak kırpar. Deneyin. Bu, her tek satırlık girdide her zaman yapmanız gereken, ama sık sık unutulan bir şeydir. Nette onu otomatik yapar. (Formu kandırmak için ad olarak çok satırlı bir dize göndermeyi deneyebilirsiniz. Burada da Nette kanmaz ve satır sonları boşluğa dönüştürülür.)

Form her zaman sunucu tarafında doğrulanır, ama bir JavaScript doğrulaması da üretilir. Bu anında çalışır ve kullanıcı, formu sunucuya göndermeye gerek kalmadan hataları hemen öğrenir. Bunu netteForms.js betiği üstlenir. Onu sayfaya ekleyin:

<script src="https://unpkg.com/nette-forms@3"></script>

Formun bulunduğu sayfanın kaynak koduna bakarsanız, Nette'in zorunlu öğeleri required CSS sınıfına sahip elemanların içine koyduğunu fark edebilirsiniz. Şablona aşağıdaki stil sayfasını eklemeyi deneyin; “Ad” etiketi kırmızıya dönecek. Bu, zorunlu öğeleri kullanıcılar için şık biçimde vurgular:

<style>
.required label { color: maroon }
</style>

Başka doğrulama kurallarını addRule() metoduyla ekleriz. İlk parametre kural, ikincisi yine hata mesajının metni, ardından da isteğe bağlı bir doğrulama kuralı argümanı gelebilir. Bu ne demek?

Formu, tam sayı olması (addInteger()) ve izin verilen bir aralıkta bulunması ($form::Range) gereken yeni ve isteğe bağlı bir “yaş” alanıyla genişletelim. Burada, gereken aralığı doğrulayıcıya [min, max] çifti olarak aktarmak için addRule() metodunun üçüncü parametresini kullanacağız:

$form->addInteger('age', 'Yaş:')
	->addRule($form::Range, 'Yaş 18 ile 120 arasında olmalıdır.', [18, 120]);

Kullanıcı alanı doldurmazsa doğrulama kuralları denetlenmez, çünkü öğe isteğe bağlıdır.

Bu, küçük bir yeniden düzenlemeye yer açar. Hata mesajında ve üçüncü parametrede sayılar yineleniyor; bu ideal değil. Çok dilli formlar yapıyor olsaydık ve sayı içeren mesaj birden çok dile çevrilseydi, değerleri değiştirmek zorlaşırdı. Bu nedenle %d yer tutucuları kullanılabilir ve Nette değerleri doldurur:

	->addRule($form::Range, 'Yaş %d ile %d yaş arasında olmalıdır.', [18, 120]);

password öğesine dönelim, onu da zorunlu yapalım ve ayrıca en az parola uzunluğunu ($form::MinLength) doğrulayalım; yine mesajda bir yer tutucu kullanarak:

$form->addPassword('password', 'Parola:')
	->setRequired('Bir parola seçin')
	->addRule($form::MinLength, 'Parola en az %d karakter uzunluğunda olmalıdır', 8);

Forma, kullanıcının doğrulama için parolayı yeniden girdiği passwordVerify adlı bir alan daha ekleyelim. Doğrulama kurallarıyla iki parolanın aynı olup olmadığını denetliyoruz ($form::Equal). Parametre olarak ilk parolaya köşeli parantezlerle bir referans veriyoruz:

$form->addPassword('passwordVerify', 'Parola tekrar:')
	->setRequired('Doğrulama için lütfen parolayı yeniden girin')
	->addRule($form::Equal, 'Parolalar eşleşmiyor', $form['password'])
	->setOmitted();

setOmitted() ile, değeri aslında bizi ilgilendirmeyen ve yalnızca doğrulama amacıyla var olan bir öğeyi işaretledik. Değeri $data içine aktarılmaz.

Böylece hem PHP hem JavaScript doğrulamalı, tam işleyen bir formumuz oldu. Nette'in doğrulama yetenekleri çok daha geniştir; koşullar oluşturabilir, onlara göre sayfanın parçalarını gösterip gizleyebilirsiniz vb. Her şeyi form doğrulama bölümünde öğreneceksiniz.

Varsayılan Değerler

Form öğeleri için sık sık varsayılan değerler ayarlarız:

$form->addEmail('email', 'E-posta')
	->setDefaultValue($lastUsedEmail);

Tüm öğeler için varsayılan değerleri aynı anda ayarlamak çoğu zaman işe yarar; örneğin form kayıt düzenlemek için kullanıldığında. Kaydı veritabanından okur ve değerlerini varsayılan olarak ayarlarız:

// $row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);

setDefaults() metodunu öğeleri tanımladıktan sonra çağırın.

Zaten gönderilmiş bir formda setDefaults() etkisizdir; kullanıcının doldurduğunun üzerine yazmaz, bu yüzden onu form factory'sinde koşulsuz çağırmak güvenlidir. Değerleri gönderimden sonra da zorlamanız gerekiyorsa bunun yerine setValues() kullanın.

Formun Render Edilmesi

Form varsayılan olarak bir tablo olarak render edilir. Tek tek öğeler temel erişilebilirlik yönergelerine uyar; tüm etiketler <label> elemanı olarak üretilir ve ilgili form öğeleriyle ilişkilendirilir. Bir etikete tıklamak imleci otomatik olarak form alanına koyar.

Her öğe için istediğimiz HTML niteliklerini ayarlayabiliriz. Örneğin bir placeholder ekleyelim:

$form->addInteger('age', 'Yaş:')
	->setHtmlAttribute('placeholder', 'Lütfen yaşı girin');

Bir formu render etmenin pek çok yolu var, bu yüzden render'a ayrı bir bölüm ayrıldı.

Latte ile Render

Elinizin altında Latte şablon motoru varsa, formu ona render ettirip ortaya çıkan HTML üzerinde tam denetim kazanabilirsiniz. Motoru oluşturur, forms extension'ını kaydeder ve formu şablona bir değişken olarak aktarırsınız:

$latte = new Latte\Engine;
$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);

$latte->render('form.latte', ['form' => $form]);

Şablonda ise formla $form değişkeni ve {input}, {label} ya da n:name gibi etiketler üzerinden çalışırsınız. Şablonu da içeren eksiksiz bir örnek örnekler dizininde bulunabilir (latte.php dosyası ve latte/ klasörü). Tek tek etiketler render bölümünde anlatılıyor.

Sınıflara Eşleme

Form verisinin işlenmesine dönelim. getValues() metodu, gönderilen veriyi bir ArrayHash nesnesi olarak döndürdü. Bu, stdClass gibi genel bir sınıf olduğundan, onunla çalışırken düzenleyicilerde özellik tamamlama ya da statik kod çözümlemesi gibi bazı kolaylıklardan yoksun kalırız. Bu, her form için, özellikleri tek tek öğeleri temsil eden özel bir sınıf yazılarak çözülebilir. Örneğin:

class RegistrationFormData
{
	public string $name;
	public ?int $age;
	public string $password;
}

Alternatif olarak yapıcıyı kullanabilirsiniz:

class RegistrationFormData
{
	public function __construct(
		public string $name,
		public ?int $age,
		public string $password,
	) {
	}
}

Veri sınıfının özellikleri enum da olabilir ve otomatik olarak eşlenirler.

Nette'e veriyi bu sınıfın nesneleri olarak döndürmesini nasıl söyleriz? Sandığınızdan kolay. Tek yapmanız gereken, parametre olarak sınıf adını ya da doldurulacak nesneyi belirtmek:

$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;

Parametre olarak 'array' da belirtebilirsiniz; o zaman veri dizi olarak döndürülür.

Formlar container'lardan oluşan çok düzeyli bir yapıdan oluşuyorsa, her biri için ayrı bir sınıf oluşturun:

$form = new Form;
$person = $form->addContainer('person');
$person->addText('firstName');
/* ... */

class PersonFormData
{
	public string $firstName;
	public string $lastName;
}

class RegistrationFormData
{
	public PersonFormData $person;
	public ?int $age;
	public string $password;
}

Eşleme, $person özelliğinin türünden container'ı PersonFormData sınıfına eşlemesi gerektiğini bilir. Özellik container dizisi içerecekse array türünü belirtin ve eşlenecek sınıfı doğrudan container'a verin:

$person->setMappedType(PersonFormData::class);

Formun veri sınıfı için bir taslağı, onu tarayıcı sayfasına yazdıran Nette\Forms\Blueprint::dataClass($form) metoduyla ürettirebilirsiniz. Sonra yalnızca tıklayıp kodu seçin ve projenize kopyalayın.

Birden Çok Gönder Düğmesi

Formun birden çok düğmesi varsa, genellikle hangisine basıldığını ayırt etmemiz gerekir. Düğmenin isSubmittedBy() metodu bu bilgiyi döndürür:

$form->addSubmit('save', 'Kaydet');
$form->addSubmit('delete', 'Sil');

if ($form->isSuccess()) {
	if ($form['save']->isSubmittedBy()) {
		// ...
	}

	if ($form['delete']->isSubmittedBy()) {
		// ...
	}
}

$form->isSuccess() denetimini atlamayın; verinin geçerliliğini o doğrular.

Bir form Enter tuşuna basılarak gönderildiğinde, ilk düğmeyle gönderilmiş gibi ele alınır.

Açıklara Karşı Koruma

Nette Framework güvenliğe büyük önem verir ve bu yüzden formların düzgün güvenliğini titizlikle sağlar.

Formları Cross-Site Scripting (XSS) ve Cross-Site Request Forgery (CSRF) gibi iyi bilinen açıklara karşı korumanın yanı sıra, artık düşünmenize gerek kalmayan pek çok küçük güvenlik önlemi de alır.

Örneğin girdilerdeki tüm denetim karakterlerini süzer ve UTF-8 kodlamasının geçerliliğini denetler; böylece formdan gelen verinin her zaman temiz olmasını sağlar. Seçim kutuları ve radyo listelerinde, seçilen öğelerin gerçekten sunulan seçenekler arasında olduğunu ve hiçbir sahtecilik yapılmadığını doğrular. Tek satırlık metin girdilerinde, bir saldırganın gönderebileceği satır sonu karakterlerini boşlukla değiştirdiğini zaten söylemiştik. Çok satırlı girdilerde satır sonu karakterlerini normalleştirir. Ve böyle sürer.

Nette, pek çok programcının var olduğunu bile bilmediği güvenlik risklerini sizin yerinize halleder.

Sözü edilen CSRF saldırısı, bir saldırganın kurbanı, kurbanın tarayıcısında sessizce, kurbanın o an oturum açmış olduğu sunucuya bir istek çalıştıran bir sayfaya çekmesinden ibarettir. Sunucu, isteğin kurban tarafından gönüllü olarak yapıldığına inanır. Bu yüzden Nette, yabancı bir kaynaktan gönderilen POST formlarını reddeder; aynı sitenin farklı bir alt alan adı bile yabancı sayılır. Başka bir kaynaktan gönderime izin vermeniz gerekiyorsa korumayı şununla kapatın:

$form->allowCrossOrigin(); // UYARI! Korumayı tümüyle kapatır!

Ancak bu, korumayı her kaynak için kapatır. Yalnızca belirli kaynaklara izin vermek için korumayı kapatın ve Origin header'ını kendi izin listenize göre kendiniz doğrulayın.

Koruma, tarayıcının otomatik gönderdiği ve bir XSS açığıyla bile taklit edilemeyen Sec-Fetch-Site header'ına (Fetch Metadata) dayanır. Bu header'ları göndermeyen eski tarayıcılar denetimi geçemez. Tarayıcı sonunda CSRF'yi çözüyor yazısı bunu ayrıntılı anlatıyor.

Oturumda saklanan bir yetkilendirme token'ıyla yapılan ve $form->addProtection() ile etkinleştirilen önceki koruma artık gerekmiyor ve 3.3 sürümünden beri kullanımdan kaldırıldı.

Böylece Nette'te formlara hızlı bir giriş yaptık. Daha fazla ilham için dağıtımdaki örnekler dizinine bakmayı deneyin.

versiyon: 4.x