Presenter'larda Formlar
Nette Forms, web formlarının oluşturulmasını ve işlenmesini önemli ölçüde kolaylaştırır. Bu bölümde, presenter'lar içinde formların kullanımını öğreneceksiniz.
Framework'ün geri kalanı olmadan tamamen bağımsız olarak nasıl kullanılacağını merak ediyorsanız, sizin için bağımsız kullanım kılavuzu bulunmaktadır.
İlk Form
Basit bir kayıt formu yazmayı deneyelim. Kodu şöyle olacaktır:
use Nette\Application\UI\Form;
$form = new Form;
$form->addText('name', 'İsim:');
$form->addPassword('password', 'Şifre:');
$form->addSubmit('send', 'Kaydol');
$form->onSuccess[] = [$this, 'formSucceeded'];
ve tarayıcıda şöyle görünecektir:

Presenter'daki form, Nette\Application\UI\Form
sınıfının bir nesnesidir, öncülü
Nette\Forms\Form
bağımsız kullanım için tasarlanmıştır. Ona isim, şifre ve gönderme düğmesi olarak
adlandırılan elemanları ekledik. Ve son olarak, $form->onSuccess
satırı, gönderildikten ve başarılı bir
şekilde doğrulandıktan sonra $this->formSucceeded()
metodunun çağrılması gerektiğini söyler.
Presenter açısından form, sıradan bir bileşendir. Bu nedenle, bir bileşen olarak ele alınır ve fabrika metotları kullanılarak presenter'a dahil edilir. Şöyle 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', 'İsim:');
$form->addPassword('password', 'Şifre:');
$form->addSubmit('send', 'Kaydol');
$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 ismi içerir
// $data->password şifreyi içerir
$this->flashMessage('Başarıyla kaydoldunuz.');
$this->redirect('Home:');
}
}
Ve şablonda formu {control}
etiketiyle render ederiz:
<h1>Kayıt</h1>
{control registrationForm}
Ve aslında hepsi bu :-) Çalışan ve mükemmel güvenli bir formumuz var.
Ve şimdi muhtemelen bunun çok hızlı olduğunu düşünüyorsunuz, formSucceeded()
metodunun nasıl
çağrıldığını ve aldığı parametrelerin ne olduğunu merak ediyorsunuz. Evet, haklısınız, bu açıklama
gerektiriyor.
Nette, Hollywood tarzı dediğimiz taze bir mekanizma ile birlikte gelir. Bir geliştirici olarak sürekli bir şeylerin olup olmadığını sormak yerine (“form gönderildi mi?”, “geçerli bir şekilde gönderildi mi?” ve “sahtesi yapılmadı mı?”), framework'e “form geçerli bir şekilde doldurulduğunda, bu metodu çağır” dersiniz ve geri kalan işi ona bırakırsınız. JavaScript'te programlama yapıyorsanız, bu programlama tarzını yakından tanırsınız. Belirli bir olay gerçekleştiğinde çağrılan fonksiyonlar yazarsınız. Ve dil onlara ilgili argümanları iletir.
Yukarıdaki presenter kodu tam olarak bu şekilde oluşturulmuştur. $form->onSuccess
dizisi, form
gönderildiğinde ve doğru bir şekilde doldurulduğunda (yani geçerli olduğunda) Nette'nin çağıracağı PHP geri
aramalarının (callback) bir listesini temsil eder. Presenter yaşam döngüsü
çerçevesinde, bu sözde bir sinyaldir, yani action*
metodundan sonra ve render*
metodundan önce
çağrılırlar. Ve her geri aramaya ilk parametre olarak formun kendisini ve ikinci parametre olarak gönderilen verileri ArrayHash nesnesi şeklinde iletir. Form nesnesine ihtiyacınız
yoksa ilk parametreyi atlayabilirsiniz. Ve ikinci parametre daha akıllı olabilir, ancak bunun hakkında daha sonra konuşacağız.
$data
nesnesi, kullanıcının doldurduğu verilerle name
ve password
anahtarlarını
içerir. Genellikle verileri doğrudan daha fazla işleme göndeririz, bu örneğin veritabanına ekleme olabilir. Ancak işleme
sırasında bir hata oluşabilir, örneğin kullanıcı adı zaten alınmış olabilir. Bu durumda, hatayı
addError()
kullanarak forma geri iletiriz ve hata mesajıyla birlikte yeniden render edilmesini sağlarız.
$form->addError('Üzgünüz, bu kullanıcı adı zaten kullanılıyor.');
onSuccess
dışında bir de onSubmit
vardır: geri aramalar, form doğru doldurulmamış olsa bile
her zaman form gönderildikten sonra çağrılır. Ve ayrıca onError
: geri aramalar yalnızca gönderim geçerli
değilse çağrılır. onSuccess
veya onSubmit
içinde formu addError()
ile geçersiz
kılsak bile çağrılırlar.
Formu işledikten sonra bir sonraki sayfaya yönlendiririz. Bu, yenile, geri düğmesiyle veya tarayıcı geçmişinde gezinerek formun istenmeyen şekilde yeniden gönderilmesini önler.
Diğer form elemanları eklemeyi deneyin.
Elemanlara Erişim
Form, presenter'ın bir bileşenidir, bizim durumumuzda registrationForm
olarak adlandırılmıştır (fabrika
metodu createComponentRegistrationForm
adına göre), bu nedenle presenter'ın herhangi bir yerinde forma şu
şekilde erişebilirsiniz:
$form = $this->getComponent('registrationForm');
// alternatif sözdizimi: $form = $this['registrationForm'];
Bireysel form elemanları da bileşenlerdir, bu nedenle onlara aynı şekilde erişebilirsiniz:
$input = $form->getComponent('name'); // veya $input = $form['name'];
$button = $form->getComponent('send'); // veya $button = $form['send'];
Elemanlar unset ile kaldırılır:
unset($form['name']);
Doğrulama Kuralları
Geçerli kelimesi geçti, ancak formun henüz herhangi bir doğrulama kuralı yok. Bunu düzeltelim.
İsim zorunlu olacak, bu yüzden onu setRequired()
metoduyla işaretleyeceğiz, argümanı kullanıcı ismi
doldurmazsa görüntülenecek hata mesajının metnidir. Argüman belirtmezsek, varsayılan hata mesajı kullanılır.
$form->addText('name', 'İsim:')
->setRequired('Lütfen ismi girin');
Formu doldurulmuş isim olmadan göndermeyi deneyin ve bir hata mesajının görüntülendiğini ve tarayıcının veya sunucunun alanı doldurana kadar reddedeceğini göreceksiniz.
Aynı zamanda, sisteme sadece boşluk yazarak hile yapamazsınız. Hayır. Nette sol ve sağ boşlukları otomatik olarak kaldırır. Deneyin. Bu, her tek satırlık girişle her zaman yapmanız gereken bir şeydir, ancak genellikle unutulur. Nette bunu otomatik olarak yapar. (Formu aldatmayı deneyebilir ve isim olarak çok satırlı bir dize gönderebilirsiniz. Nette burada da aldanmaz ve satır sonlarını boşluklara dönüştürür.)
Form her zaman sunucu tarafında doğrulanır, ancak aynı zamanda anında gerçekleşen ve kullanıcının hatayı formu
sunucuya göndermeye gerek kalmadan hemen öğrendiği JavaScript doğrulaması da üretilir. Bu, netteForms.js
betiği tarafından yapılır. Bunu layout şablonuna ekleyin:
<script src="http://unpkg.com/nette-forms@3"></script>
Form içeren sayfanın kaynak koduna bakarsanız, Nette'nin zorunlu elemanları required
CSS sınıfına sahip
elemanlara eklediğini fark edebilirsiniz. Şablona aşağıdaki stil sayfasını eklemeyi deneyin ve “İsim” etiketi
kırmızı olacaktır. Bu şekilde kullanıcılara zorunlu elemanları zarifçe işaretleriz:
<style>
.required label { color: maroon }
</style>
Diğer doğrulama kurallarını addRule()
metoduyla ekleriz. İlk parametre kuraldır, ikincisi yine hata
mesajının metnidir ve bunu doğrulama kuralının bir argümanı takip edebilir. Bununla ne kastediliyor?
Formu, tamsayı olması gereken (addInteger()
) ve ayrıca izin verilen bir aralıkta ($form::Range
)
olması gereken yeni isteğe bağlı “yaş” alanıyla genişleteceğiz. Ve burada tam olarak addRule()
metodunun
üçüncü parametresini kullanacağız, bununla doğrulayıcıya istenen aralığı [başlangıç, bitiş]
çifti
olarak ileteceğiz:
$form->addInteger('age', 'Yaş:')
->addRule($form::Range, 'Yaş 18 ile 120 arasında olmalıdır', [18, 120]);
Kullanıcı alanı doldurmazsa, eleman isteğe bağlı olduğu için doğrulama kuralları kontrol edilmeyecektir.
Burada küçük bir yeniden düzenleme için yer var. Hata mesajında ve üçüncü parametrede sayılar yinelenmiştir, bu
ideal değildir. Eğer çok dilli formlar oluşturuyor olsaydık
ve sayıları içeren mesaj birden çok dile çevrilmiş olsaydı, değerlerin olası bir değişikliği zorlaşırdı. Bu
nedenle, %d
yer tutucularını kullanmak mümkündür ve Nette değerleri tamamlayacaktır:
->addRule($form::Range, 'Yaş %d ile %d arasında olmalıdır', [18, 120]);
Aynı zamanda zorunlu hale getireceğimiz ve ayrıca şifrenin minimum uzunluğunu ($form::MinLength
)
doğrulayacağımız password
elemanına geri dönelim, yine yer tutucu kullanarak:
$form->addPassword('password', 'Şifre:')
->setRequired('Bir şifre seçin')
->addRule($form::MinLength, 'Şifre en az %d karakter olmalıdır', 8);
Forma bir de passwordVerify
alanı ekleyelim, burada kullanıcı kontrol için şifreyi tekrar girecektir.
Doğrulama kurallarını kullanarak her iki şifrenin aynı olup olmadığını kontrol edeceğiz ($form::Equal
). Ve
parametre olarak ilk şifreye köşeli parantezler kullanarak bir referans vereceğiz:
$form->addPassword('passwordVerify', 'Kontrol için şifre:')
->setRequired('Lütfen kontrol için şifreyi tekrar girin')
->addRule($form::Equal, 'Şifreler eşleşmiyor', $form['password'])
->setOmitted();
setOmitted()
kullanarak, değerinin aslında bizim için önemli olmadığı ve yalnızca doğrulama amacıyla var
olan elemanı işaretledik. Değer $data
'ya iletilmez.
Böylece PHP ve JavaScript'te doğrulaması olan tamamen işlevsel bir formumuz oldu. Nette'nin doğrulama yetenekleri çok daha geniştir, koşullar oluşturabilir, bunlara göre sayfanın bölümlerini gösterebilir ve gizleyebilirsiniz vb. Her şeyi form doğrulaması bölümünde öğreneceksiniz.
Varsayılan Değerler
Form elemanlarına genellikle varsayılan değerler atarız:
$form->addEmail('email', 'E-posta')
->setDefaultValue($lastUsedEmail);
Genellikle tüm elemanlara aynı anda varsayılan değerler atamak kullanışlıdır. Örneğin, form kayıtları düzenlemek için kullanıldığında. Veritabanından kaydı okur ve varsayılan değerleri atarız:
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
setDefaults()
'u elemanları tanımladıktan sonra çağırın.
Formun Render Edilmesi
Standart olarak form bir tablo olarak render edilir. Bireysel elemanlar temel erişilebilirlik kuralını karşılar – tüm
etiketler <label>
olarak yazılır ve ilgili form elemanıyla ilişkilendirilir. Etikete tıklandığında
imleç otomatik olarak form alanında görünür.
Her elemana istediğimiz HTML niteliklerini atayabiliriz. Örneğin bir yer tutucu ekleyebiliriz:
$form->addInteger('age', 'Yaş:')
->setHtmlAttribute('placeholder', 'Lütfen yaşı girin');
Formu render etmenin gerçekten çok sayıda yolu vardır, bu nedenle buna ayrılmış renderleme hakkında ayrı bir bölüm bulunmaktadır.
Sınıflara Eşleme
İkinci parametre $data
'da gönderilen verileri ArrayHash
nesnesi olarak alan
formSucceeded()
metoduna geri dönelim. Bu, stdClass
gibi genel bir sınıf olduğundan, onunla
çalışırken belirli bir konfor eksikliği yaşayacağız, örneğin editörlerde özelliklerin önerilmesi veya statik kod
analizi gibi. Bu, her form için özelliklerinin bireysel elemanları temsil ettiği belirli bir sınıfa sahip olarak
çö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'lar da olabilir ve otomatik olarak eşlenirler.
Nette'ye verileri bu sınıfın nesneleri olarak döndürmesini nasıl söyleriz? Düşündüğünüzden daha kolay. Sınıfı
işleyici metodundaki $data
parametresinin türü olarak belirtmek yeterlidir:
public function formSucceeded(Form $form, RegistrationFormData $data): void
{
// $data, RegistrationFormData örneğidir
$name = $data->name;
// ...
}
Tür olarak array
de belirtebilirsiniz ve o zaman verileri bir dizi olarak iletir.
Benzer şekilde, sınıf adını veya hidratlanacak nesneyi parametre olarak ilettiğimiz getValues()
fonksiyonunu
da kullanabilirsiniz:
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
Formlar konteynerlerden oluşan çok seviyeli bir yapı oluşturuyorsa, 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
özelliğinin türünden, konteyneri PersonFormData
sınıfına eşlemesi
gerektiğini anlar. Eğer özellik bir konteyner dizisi içeriyorsa, array
türünü belirtin ve eşleme için
sınıfı doğrudan konteynere iletin:
$person->setMappedType(PersonFormData::class);
Formun veri sınıfının tasarımını Nette\Forms\Blueprint::dataClass($form)
metodunu
kullanarak oluşturabilirsiniz, bu da onu tarayıcı sayfasına yazdırır. Kodu daha sonra tıklayarak işaretleyip projeye
kopyalamak yeterlidir.
Birden Fazla Düğme
Formun birden fazla düğmesi varsa, genellikle hangisine basıldığını ayırt etmemiz gerekir. Her düğme için kendi
işleyici fonksiyonumuzu oluşturabiliriz. Bunu olay
onClick
için bir işleyici olarak ayarlayacağız:
$form->addSubmit('save', 'Kaydet')
->onClick[] = [$this, 'saveButtonPressed'];
$form->addSubmit('delete', 'Sil')
->onClick[] = [$this, 'deleteButtonPressed'];
Bu işleyiciler, tıpkı onSuccess
olayında olduğu gibi, yalnızca geçerli bir şekilde doldurulmuş form
durumunda çağrılır. Fark, ilk parametre olarak form yerine gönderme düğmesinin iletilebilmesidir, belirttiğiniz türe
bağlıdır:
public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data)
{
$form = $button->getForm();
// ...
}
Form Enter tuşuyla gönderildiğinde, ilk düğmeyle gönderilmiş gibi kabul edilir.
onAnchor Olayı
Fabrika metodunda (örneğin createComponentRegistrationForm
gibi) formu oluştururken, form henüz gönderilip
gönderilmediğini veya hangi verilerle gönderildiğini bilmez. Ancak gönderilen değerleri bilmemiz gereken durumlar vardır,
örneğin formun sonraki şekli bunlara bağlıdır veya bağımlı seçme kutuları (select box) için onlara ihtiyacımız
vardır vb.
Bu nedenle, formu oluşturan kodun bir kısmını, yalnızca sözde demirlendiğinde, yani presenter ile zaten bağlantılı
olduğunda ve gönderilen verilerini bildiğinde çağrılmasını sağlayabilirsiniz. Böyle bir kodu $onAnchor
dizisine iletiriz:
$country = $form->addSelect('country', 'Ülke:', $this->model->getCountries());
$city = $form->addSelect('city', 'Şehir:');
$form->onAnchor[] = function () use ($country, $city) {
// bu fonksiyon, formun gönderilip gönderilmediğini ve hangi verilerle gönderildiğini bildiğinde çağrılır
// bu nedenle getValue() metodu kullanılabilir
$val = $country->getValue();
$city->setItems($val ? $this->model->getCities($val) : []);
};
Güvenlik Açıklarına Karşı Koruma
Nette Framework güvenliğe büyük önem verir ve bu nedenle formların iyi bir şekilde korunmasına özen gösterir. Bunu tamamen şeffaf bir şekilde yapar ve manuel olarak hiçbir şey ayarlamayı gerektirmez.
Formları Cross Site Scripting (XSS) ve Cross-Site Request Forgery (CSRF) saldırılarına karşı korumanın yanı sıra, sizin artık düşünmeniz gerekmeyen birçok küçük güvenlik önlemi alır.
Ö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çme kutuları (select box) ve radyo listelerinde, seçilen öğelerin gerçekten sunulanlardan olduğunu ve sahtesinin yapılmadığını doğrular. Tek satırlık metin girişlerinde, saldırganın oraya göndermiş olabileceği satır sonu karakterlerini kaldırdığını zaten belirtmiştik. Çok satırlık girişlerde ise satır sonu karakterlerini normalleştirir. Ve böyle devam eder.
Nette, birçok programcının varlığından bile haberdar olmadığı güvenlik risklerini sizin için çözer.
Bahsedilen CSRF saldırısı, bir saldırganın kurbanı, kurbanın oturum açtığı sunucuya kurbanın tarayıcısında fark ettirmeden bir istek gerçekleştiren bir sayfaya çekmesinden oluşur ve sunucu, isteğin kurban tarafından kendi isteğiyle gerçekleştirildiğini varsayar. Bu nedenle Nette, POST formunun başka bir alan adından gönderilmesini engeller. Herhangi bir nedenle korumayı kapatmak ve formun başka bir alan adından gönderilmesine izin vermek isterseniz, şunu kullanın:
$form->allowCrossOrigin(); // DİKKAT! Korumayı kapatır!
Bu koruma, _nss
adlı SameSite çerezini kullanır. SameSite çereziyle koruma %100 güvenilir olmayabilir, bu
nedenle token ile korumayı da etkinleştirmek önerilir:
$form->addProtection();
Uygulamadaki hassas verileri değiştiren sitenin yönetim bölümündeki formları bu şekilde korumanızı öneririz.
Framework, oturumda saklanan bir yetkilendirme token'ı üreterek ve doğrulayarak CSRF saldırısına karşı kendini savunur. Bu
nedenle, formu görüntülemeden önce oturumun açık olması gerekir. Sitenin yönetim bölümünde, genellikle kullanıcı
girişi nedeniyle oturum zaten başlatılmıştır. Aksi takdirde, oturumu Nette\Http\Session::start()
metoduyla
başlatın.
Birden Fazla Presenter'da Aynı Form
Bir formu birden fazla presenter'da kullanmanız gerekiyorsa, bunun için bir fabrika oluşturmanızı ve ardından bunu
presenter'a iletmenizi öneririz. Böyle bir sınıf için uygun bir konum, örneğin app/Forms
dizinidir.
Fabrika sınıfı şöyle görünebilir:
use Nette\Application\UI\Form;
class SignInFormFactory
{
public function create(): Form
{
$form = new Form;
$form->addText('name', 'İsim:');
$form->addSubmit('send', 'Giriş yap');
return $form;
}
}
Sınıftan, presenter'daki bileşenler için fabrika metodunda formu üretmesini isteriz:
public function __construct(
private SignInFormFactory $formFactory,
) {
}
protected function createComponentSignInForm(): Form
{
$form = $this->formFactory->create();
// formu değiştirebiliriz, burada örneğin düğme üzerindeki etiketi değiştiriyoruz
$form['send']->setCaption('Devam et');
$form->onSuccess[] = [$this, 'signInFormSuceeded']; // ve bir işleyici ekliyoruz
return $form;
}
Form işleme için işleyici, fabrikadan da sağlanabilir:
use Nette\Application\UI\Form;
class SignInFormFactory
{
public function create(): Form
{
$form = new Form;
$form->addText('name', 'İsim:');
$form->addSubmit('send', 'Giriş yap');
$form->onSuccess[] = function (Form $form, $data): void {
// burada form işlemeyi gerçekleştiriyoruz
};
return $form;
}
}
İşte, Nette'deki formlara hızlı bir giriş yaptık. Dağıtımdaki examples dizinine göz atmayı deneyin, burada daha fazla ilham bulacaksınız.