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.