Sunuculardaki Formlar

Nette Forms, web formları oluşturmayı ve işlemeyi önemli ölçüde kolaylaştırır. Bu bölümde, formları sunumların içinde nasıl kullanacağınızı öğreneceksiniz.

Çerçevenin geri kalanı olmadan bunları tamamen bağımsız olarak kullanmakla ilgileniyorsanız, bağımsız formlar için bir kılavuz vardır.

İlk Form

Basit bir kayıt formu yazmaya çalışacağız. Kodu şu şekilde görünecektir:

use Nette\Application\UI\Form;

$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');
$form->onSuccess[] = [$this, 'formSucceeded'];

ve tarayıcıda sonuç aşağıdaki gibi görünmelidir:

Sunucudaki form Nette\Application\UI\Form sınıfının bir nesnesidir, selefi Nette\Forms\Form bağımsız kullanım için tasarlanmıştır. Biz buna isim, şifre ve gönderme butonu alanlarını ekledik. Son olarak, $form->onSuccess satırında form gönderildikten ve başarılı bir doğrulama yapıldıktan sonra $this->formSucceeded() metodunun çağrılması gerektiği yazıyor.

Sunucunun bakış açısından, form ortak bir bileşendir. Bu nedenle, bir bileşen olarak ele alınır ve fabrika yöntemi kullanılarak sunucuya dahil edilir. Bu şekilde görünecektir:

use Nette;
use Nette\Application\UI\Form;

class HomePresenter extends Nette\Application\UI\Presenter
{
	protected function createComponentRegistrationForm(): Form
	{
		$form = new Form;
		$form->addText('name', 'Name:');
		$form->addPassword('password', 'Password:');
		$form->addSubmit('send', 'Sign up');
		$form->onSuccess[] = [$this, 'formSucceeded'];
		return $form;
	}

	public function formSucceeded(Form $form, $data): void
	{
		// burada form tarafından gönderilen verileri işleyeceğiz
		// $data->name isim içeriyor
		// $data->password parola içerir
		$this->flashMessage('Başarıyla kaydoldunuz.');
		$this->redirect('Home:');
	}
}

Ve şablonda render işlemi {control} etiketi kullanılarak yapılır:

<h1>Registration</h1>

{control registrationForm}

Ve hepsi bu kadar :-) İşlevsel ve mükemmel şekilde güvence altına alınmış bir formumuz var.

Şimdi muhtemelen bunun çok hızlı olduğunu düşünüyor, formSucceeded() yönteminin nasıl çağrıldığını ve aldığı parametrelerin ne olduğunu merak ediyorsunuz. Elbette haklısınız, bu bir açıklamayı hak ediyor.

Nette, Hollywood tarzı olarak adlandırdığımız harika bir mekanizma ile geliyor. Sürekli olarak bir şey olup olmadığını sormak zorunda kalmak yerine (“form gönderildi mi?”, “geçerli bir şekilde gönderildi mi?” veya “sahte değil mi?”), framework'e “form geçerli bir şekilde tamamlandığında, bu yöntemi çağır” diyorsunuz ve üzerinde daha fazla çalışmayı bırakıyorsunuz. JavaScript'te programlama yapıyorsanız, bu tarz programlamaya aşinasınızdır. Belirli bir olay gerçekleştiğinde çağrılan fonksiyonlar yazarsınız. Ve dil onlara uygun argümanları iletir.

Yukarıdaki sunum kodu bu şekilde oluşturulmuştur. $form->onSuccess dizisi, form gönderildiğinde ve doğru şekilde doldurulduğunda Nette'in çağıracağı PHP geri aramalarının listesini temsil eder. Sunucunun yaşam döngüsü içinde bu bir sinyal olarak adlandırılır, bu nedenle action* yönteminden sonra ve render* yönteminden önce çağrılırlar. Ve her geri aramaya ilk parametre olarak formun kendisini ve ikinci parametre olarak da ArrayHash nesnesi olarak gönderilen verileri aktarır. Form nesnesine ihtiyacınız yoksa ilk parametreyi atlayabilirsiniz. İkinci parametre daha da kullanışlı olabilir, ancak bunu daha sonra anlatacağız.

$data nesnesi, kullanıcı tarafından girilen verilerle birlikte name ve password özelliklerini içerir. Genellikle verileri, örneğin veritabanına ekleme gibi daha ileri işlemler için doğrudan göndeririz. Ancak, işleme sırasında bir hata oluşabilir, örneğin kullanıcı adı zaten alınmış olabilir. Bu durumda, addError() adresini kullanarak hatayı forma geri iletiriz ve bir hata mesajıyla birlikte yeniden çizilmesine izin veririz:

$form->addError('Sorry, username is already in use.');

Buna ek olarak onSuccess, ayrıca onSubmit: form doğru doldurulmamış olsa bile form gönderildikten sonra geri aramalar her zaman çağrılır. Ve son olarak onError: geri aramalar yalnızca gönderim geçerli değilse çağrılır. Hatta onSuccess veya onSubmit adresindeki formu addError() adresini kullanarak geçersiz kılsak bile çağrılırlar.

Formu işledikten sonra bir sonraki sayfaya yönlendireceğiz. Bu, formun yenile, geri düğmesine tıklanarak veya tarayıcı geçmişi taşınarak istenmeden yeniden gönderilmesini önler.

Daha fazla form denetimi eklemeyi deneyin.

Kontrollere Erişim

Form, sunucunun bir bileşenidir, bizim durumumuzda registrationForm olarak adlandırılmıştır (fabrika yönteminin adından sonra createComponentRegistrationForm), bu nedenle sunucunun herhangi bir yerinde formu kullanarak forma ulaşabilirsiniz:

$form = $this->getComponent('registrationForm');
// alternatif sözdizimi: $form = $this['registrationForm'];

Ayrıca bireysel form kontrolleri de bileşenlerdir, bu nedenle onlara da aynı şekilde erişebilirsiniz:

$input = $form->getComponent('name'); // veya $input = $form['name'];
$button = $form->getComponent('send'); // veya $button = $form['send'];

Kontroller unset kullanılarak kaldırılır:

unset($form['name']);

Doğrulama Kuralları

Geçerli* kelimesi birkaç kez kullanıldı, ancak formun henüz doğrulama kuralları yok. Hadi düzeltelim.

Ad zorunlu olacaktır, bu nedenle onu, argümanı kullanıcı doldurmazsa görüntülenecek hata mesajının metni olan setRequired() yöntemiyle işaretleyeceğiz. Herhangi bir argüman verilmezse, varsayılan hata mesajı kullanılır.

$form->addText('name', 'Name:')
	->setRequired('Please fill your name.');

İsim doldurulmadan formu göndermeye çalıştığınızda bir hata mesajının görüntülendiğini ve siz doldurana kadar tarayıcı veya sunucunun formu reddettiğini göreceksiniz.

Aynı zamanda, örneğin girişe sadece boşluk yazarak sistemi aldatamazsınız. Mümkün değil. Nette sol ve sağ boşlukları otomatik olarak keser. Bunu deneyin. Bu, her tek satırlık girişte her zaman yapmanız gereken bir şeydir, ancak genellikle unutulur. Nette bunu otomatik olarak yapar. (Formları kandırmayı deneyebilir ve isim olarak çok satırlı bir dize gönderebilirsiniz. Burada bile Nette aldanmayacak ve satır sonları boşluk olarak değişecektir).

Form her zaman sunucu tarafında doğrulanır, ancak JavaScript doğrulaması da oluşturulur, bu da hızlıdır ve kullanıcı formu sunucuya göndermek zorunda kalmadan hatayı hemen öğrenir. Bu işlem netteForms.js komut dosyası tarafından gerçekleştirilir. Bunu düzen şablonuna ekleyin:

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

Formun bulunduğu sayfanın kaynak koduna bakarsanız, Nette'in gerekli alanları required CSS sınıfına sahip öğelere eklediğini fark edebilirsiniz. Aşağıdaki stili şablona eklemeyi deneyin, “Ad” etiketi kırmızı olacaktır. Kullanıcılar için gerekli alanları zarif bir şekilde işaretliyoruz:

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

Ek doğrulama kuralları addRule() yöntemi ile eklenecektir. İlk parametre kuraldır, ikincisi yine hata mesajının metnidir ve isteğe bağlı doğrulama kuralı argümanı takip edebilir. Bu ne anlama geliyor?

Form, bir sayı olması (addInteger()) ve belirli sınırlar içinde olması ($form::Range) koşuluyla başka bir isteğe bağlı girdi yaş alacaktır. Ve burada addRule()'un üçüncü argümanı olan aralığın kendisini kullanacağız:

$form->addInteger('age', 'Age:')
	->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]);

Kullanıcı alanı doldurmazsa, alan isteğe bağlı olduğu için doğrulama kuralları doğrulanmayacaktır.

Açıkçası küçük bir yeniden düzenleme için yer mevcut. Hata mesajında ve üçüncü parametrede, sayılar çift olarak listelenmiştir ve bu ideal değildir. Çok dilli bir form oluşturuyor olsaydık ve sayıları içeren mesajın birden fazla dile çevrilmesi gerekseydi, değerleri değiştirmek daha zor olurdu. Bu nedenle, %d ikame karakterleri kullanılabilir:

	->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]);

Parola* alanına geri dönelim, bunu gerekli yapalım ve yine mesajdaki yedek karakterleri kullanarak minimum parola uzunluğunu ($form::MinLength) doğrulayalım:

$form->addPassword('password', 'Password:')
	->setRequired('Pick a password')
	->addRule($form::MinLength, 'Your password has to be at least %d long', 8);

Kontrol için kullanıcının şifreyi tekrar girdiği forma passwordVerify şeklinde bir alan ekleyeceğiz. Doğrulama kurallarını kullanarak, her iki şifrenin de aynı olup olmadığını kontrol ediyoruz ($form::Equal). Ve bir argüman olarak köşeli parantez kullanarak ilk şifreye bir referans veriyoruz:

$form->addPassword('passwordVerify', 'Password again:')
	->setRequired('Fill your password again to check for typo')
	->addRule($form::Equal, 'Password mismatch', $form['password'])
	->setOmitted();

setOmitted() adresini kullanarak, değerini gerçekten önemsemediğimiz ve yalnızca doğrulama için var olan bir öğeyi işaretledik. Değeri $data adresine aktarılmaz.

PHP ve JavaScript'te doğrulama ile tamamen işlevsel bir formumuz var. Nette'nin doğrulama yetenekleri çok daha geniştir, koşullar oluşturabilir, bunlara göre bir sayfanın bölümlerini görüntüleyebilir ve gizleyebilirsiniz, vb. Her şeyi form doğrul ama bölümünde bulabilirsiniz.

Varsayılan Değerler

Form denetimleri için genellikle varsayılan değerler ayarlarız:

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

Genellikle tüm kontroller için varsayılan değerleri aynı anda ayarlamak yararlıdır. Örneğin, form kayıtları düzenlemek için kullanıldığında. Kaydı veritabanından okuruz ve varsayılan değerler olarak ayarlarız:

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

Kontrolleri tanımladıktan sonra setDefaults() adresini çağırın.

Formun Oluşturulması

Varsayılan olarak, form bir tablo olarak işlenir. Bireysel kontroller temel web erişilebilirlik yönergelerini takip eder. Tüm etiketler şu şekilde oluşturulur <label> elemanlarıdır ve girişleriyle ilişkilidir, etikete tıklamak imleci girişe taşır.

Her öğe için herhangi bir HTML niteliği ayarlayabiliriz. Örneğin, bir yer tutucu ekleyin:

$form->addInteger('age', 'Age:')
	->setHtmlAttribute('placeholder', 'Please fill in the age');

Bir formu oluşturmanın gerçekten pek çok yolu vardır, bu yüzden bu bölüm oluşturma konusuna ayrılmıştır.

Sınıflarla Eşleme

İkinci parametre olarak $data gönderilen veriyi bir ArrayHash nesnesi olarak alan formSucceeded() yöntemine geri dönelim. Bu stdClass gibi genel bir sınıf olduğundan, onunla çalışırken editörlerdeki özellikler için kod tamamlama veya statik kod analizi gibi bazı kolaylıklardan yoksun kalacağız. Bu, her form için, özellikleri ayrı kontrolleri temsil eden özel bir sınıfa sahip olarak çözülebilir. Örneğin:

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

Alternatif olarak, kurucuyu kullanabilirsiniz:

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

Veri sınıfının özellikleri de enum olabilir ve otomatik olarak eşleştirilirler.

Nette'e verileri bu sınıfın nesneleri olarak döndürmesini nasıl söyleyebilirim? Düşündüğünüzden daha kolay. Tek yapmanız gereken, işleyicideki $data parametresinin türü olarak sınıfı belirtmektir:

public function formSucceeded(Form $form, RegistrationFormData $data): void
{
	// $name is instance of RegistrationFormData
	$name = $data->name;
	// ...
}

Ayrıca tür olarak array belirtebilirsiniz ve ardından verileri bir dizi olarak iletir.

Benzer şekilde, parametre olarak hidrate edilecek bir sınıf adı veya nesne olarak ilettiğimiz getValues() yöntemini kullanabilirsiniz:

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

Formlar konteynerlerden oluşan çok seviyeli 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 daha sonra $person özellik türünden konteyneri PersonFormData sınıfına eşlemesi gerektiğini bilir. Özellik bir kapsayıcı dizisi içerecekse, array türünü sağlayın ve doğrudan kapsayıcıyla eşlenecek sınıfı iletin:

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

Tarayıcı sayfasına yazdıracak olan Nette\Forms\Blueprint::dataClass($form) yöntemini kullanarak bir formun veri sınıfı için bir öneri oluşturabilirsiniz. Daha sonra kodu seçmek ve projenize kopyalamak için tıklamanız yeterlidir.

Çoklu Gönder Düğmeleri

Formda birden fazla düğme varsa, genellikle hangisine basıldığını ayırt etmemiz gerekir. Her düğme için kendi fonksiyonumuzu oluşturabiliriz. Bunu onClick olayı için bir işleyici olarak ayarlayın:

$form->addSubmit('save', 'Save')
	->onClick[] = [$this, 'saveButtonPressed'];

$form->addSubmit('delete', 'Delete')
	->onClick[] = [$this, 'deleteButtonPressed'];

Bu işleyiciler de onSuccess olayında olduğu gibi yalnızca formun geçerli olması durumunda çağrılır. Aradaki fark, belirttiğiniz türe bağlı olarak ilk parametrenin form yerine gönder düğmesi nesnesi olabilmesidir:

public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data)
{
	$form = $button->getForm();
	// ...
}

Bir form Enter tuşu ile gönderildiğinde, ilk düğme ile gönderilmiş gibi işlem görür.

Olay onAnchor

Bir fabrika yönteminde ( createComponentRegistrationForm gibi) bir form oluşturduğunuzda, henüz gönderilip gönderilmediğini veya hangi verilerle gönderildiğini bilmez. Ancak, gönderilen değerleri bilmemiz gereken durumlar vardır, belki de formun nasıl görüneceği onlara bağlıdır veya bağımlı seçim kutuları vb. için kullanılırlar.

Bu nedenle, formu oluşturan kodun, form bağlandığında çağrılmasını sağlayabilirsiniz, yani form zaten sunum yapan kişiye bağlıdır ve gönderilen verileri bilir. Böyle bir kodu $onAnchor dizisine koyacağız:

$country = $form->addSelect('country', 'Country:', $this->model->getCountries());
$city = $form->addSelect('city', 'City:');

$form->onAnchor[] = function () use ($country, $city) {
	// form gönderildiği veriyi bildiğinde bu fonksiyon çağrılacaktır
	// böylece getValue() yöntemini kullanabilirsiniz
	$val = $country->getValue();
	$city->setItems($val ? $this->model->getCities($val) : []);
};

Güvenlik Açığı Koruması

Nette Framework güvenli olmak için büyük çaba sarf eder ve formlar en yaygın kullanıcı girdisi olduğundan, Nette formları aşılmaz kadar iyidir. Her şey dinamik ve şeffaf olarak korunur, hiçbir şeyin manuel olarak ayarlanması gerekmez.

Formları Siteler Arası Komut Dosyası Yazma (XSS ) ve Siteler Arası İstek Sahteciliği (CSRF) gibi iyi bilinen güvenlik açıklarını hedef alan saldırılara karşı korumanın yanı sıra, artık düşünmek zorunda olmadığınız birçok küçük güvenlik görevini de yerine getirir.

Örneğin, girdilerden tüm kontrol karakterlerini filtreler ve UTF-8 kodlamasının geçerliliğini kontrol eder, böylece formdan gelen veriler her zaman temiz olur. Seçim kutuları ve radyo listeleri için, seçilen öğelerin gerçekten sunulanlardan olduğunu ve herhangi bir sahtecilik olmadığını doğrular. Tek satırlı metin girişi için, bir saldırganın oraya gönderebileceği satır sonu karakterlerini kaldırdığından daha önce bahsetmiştik. Çok satırlı girdiler için satır sonu karakterlerini normalleştirir. Ve böyle devam eder.

Nette, çoğu programcının varlığından bile haberdar olmadığı güvenlik açıklarını sizin için düzeltir.

Bahsedilen CSRF saldırısı, bir saldırganın kurbanı, kurbanın tarayıcısında kurbanın o anda oturum açtığı sunucuya sessizce bir istek yürüten bir sayfayı ziyaret etmesi için kandırması ve sunucunun, isteğin kurban tarafından isteğe bağlı olarak yapıldığına inanmasıdır. Bu nedenle Nette, formun başka bir etki alanından POST yoluyla gönderilmesini engeller. Herhangi bir nedenle korumayı kapatmak ve formun başka bir etki alanından gönderilmesine izin vermek istiyorsanız, şunu kullanın:

$form->allowCrossOrigin(); // DİKKAT! Korumayı kapatır!

Bu koruma, _nss adlı bir SameSite çerezi kullanır. SameSite çerez koruması %100 güvenilir olmayabilir, bu nedenle token korumasını açmak iyi bir fikirdir:

$form->addProtection();

Bu korumayı uygulamanızın hassas verileri değiştiren idari bölümündeki formlara uygulamanız şiddetle tavsiye edilir. Çerçeve, bir oturumda saklanan kimlik doğrulama belirtecini oluşturarak ve doğrulayarak CSRF saldırısına karşı koruma sağlar (argüman, belirtecin süresi dolmuşsa gösterilen hata mesajıdır). Bu nedenle formu görüntülemeden önce bir oturumun başlatılmış olması gerekir. Web sitesinin yönetim bölümünde, kullanıcının oturum açması nedeniyle oturum genellikle zaten başlatılmıştır. Aksi takdirde, oturumu Nette\Http\Session::start() yöntemi ile başlatın.

Bir Formu Birden Fazla Sunucuda Kullanma

Bir formu birden fazla sunucuda kullanmanız gerekiyorsa, bunun için daha sonra sunucuya aktaracağınız bir fabrika oluşturmanızı öneririz. Böyle bir sınıf için uygun bir konum, örneğin, app/Forms dizinidir.

Fabrika sınıfı şu şekilde görünebilir:

use Nette\Application\UI\Form;

class SignInFormFactory
{
	public function create(): Form
	{
		$form = new Form;
		$form->addText('name', 'Name:');
		$form->addSubmit('send', 'Log in');
		return $form;
	}
}

Sunucu içerisindeki bileşenler için fabrika metodunda sınıfın formu üretmesini istiyoruz:

public function __construct(
	private SignInFormFactory $formFactory,
) {
}

protected function createComponentSignInForm(): Form
{
	$form = $this->formFactory->create();
	// formu değiştirebiliriz, burada örneğin düğmenin üzerindeki etiketi değiştiriyoruz
	$form['login']->setCaption('Continue');
	$form->onSuccess[] = [$this, 'signInFormSubmitted']; // ve işleyici ekleyin
	return $form;
}

Form işleme işleyicisi fabrikadan da teslim edilebilir:

use Nette\Application\UI\Form;

class SignInFormFactory
{
	public function create(): Form
	{
		$form = new Form;
		$form->addText('name', 'Name:');
		$form->addSubmit('send', 'Log in');
		$form->onSuccess[] = function (Form $form, $data): void {
			// gönderilen formumuzu burada işliyoruz
		};
		return $form;
	}
}

Böylece, Nette'deki formlara hızlı bir giriş yapmış olduk. Daha fazla ilham almak için dağıtımdaki örnekler dizinine bakmayı deneyin.

versiyon: 4.0