Formları Birden Çok Yerde Yeniden Kullanma
Nette, aynı formu kod yinelemeden birden çok yerde kullanmanın çeşitli yollarını sunar. Bu yazı, kaçınmanız gerekenler de dahil olmak üzere çeşitli çözümleri ele alıyor.
Form Factory'si
Bir bileşeni birden çok yerde yeniden kullanmanın temel yaklaşımı, bu bileşeni üreten bir metot ya da sınıf oluşturmaktır. Bu metot daha sonra uygulamanın çeşitli yerlerinden çağrılır. Böyle bir metoda ya da sınıfa factory denir. Lütfen bunu, factory kullanımının belirli bir biçimini anlatan ve bu konuyla doğrudan ilgisi olmayan factory method tasarım deseniyle karıştırmayın.
Örnek olarak, bir düzenleme formu kuran bir factory oluşturalım:
use Nette\Application\UI\Form;
class FormFactory
{
public function createEditForm(): Form
{
$form = new Form;
$form->addText('title', 'Başlık:');
// diğer form alanları buraya eklenir
$form->addSubmit('send', 'Kaydet');
return $form;
}
}
Artık bu factory'yi uygulamanızın çeşitli yerlerinde, örneğin presenter'larda ya da bileşenlerde kullanabilirsiniz. Bunu, onu bağımlılık olarak isteyerek yaparsınız. Önce sınıfı yapılandırma dosyasında kaydedin:
services:
- FormFactory
Sonra onu bir presenter'da kullanın:
class MyPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private FormFactory $formFactory,
) {
}
protected function createComponentEditForm(): Form
{
$form = $this->formFactory->createEditForm();
$form->onSuccess[] = function () {
// gönderilen verilerin işlenmesi
};
return $form;
}
}
Form factory'sini, uygulamanızın ihtiyacına göre başka form türleri oluşturan metotlarla genişletebilirsiniz. Doğal olarak, öğesiz temel bir form oluşturan ve diğer metotların yararlanabileceği bir metot da ekleyebiliriz:
class FormFactory
{
public function createForm(): Form
{
$form = new Form;
return $form;
}
public function createEditForm(): Form
{
$form = $this->createForm();
$form->addText('title', 'Başlık:');
// diğer form alanları buraya eklenir
$form->addSubmit('send', 'Kaydet');
return $form;
}
}
createForm() metodu henüz pek yararlı bir şey yapmıyor, ama bu yakında değişecek.
Factory Bağımlılıkları
Zamanla formların çok dilli olması gerekebilir. Bu da tüm formlar için bir çevirmen ayarlamak demektir. Bunu sağlamak için
FormFactory sınıfını, Translator nesnesini yapıcısında bağımlılık olarak alacak ve
oluşturulan forma aktaracak şekilde değiştirin:
use Nette\Localization\Translator;
class FormFactory
{
public function __construct(
private Translator $translator,
) {
}
public function createForm(): Form
{
$form = new Form;
$form->setTranslator($this->translator);
return $form;
}
// ...
}
createForm() metodu, belirli formları oluşturan diğer metotlar tarafından da çağrıldığından, çevirmeni
burada ayarlamak yeterlidir. Ve iş bitti. Hiçbir presenter ya da bileşen kodunu değiştirmeye gerek yok; bu harika.
Daha Fazla Factory Sınıfı
Alternatif olarak, uygulamanızda kullanmayı düşündüğünüz her form için ayrı factory sınıfları
oluşturabilirsiniz. Bu yaklaşım kodun okunurluğunu artırabilir ve form yönetimini kolaylaştırabilir. Özgün
FormFactory yalnızca temel yapılandırmayla (çeviri desteği gibi) basit bir form oluştursun; düzenleme formu
için de EditFormFactory adlı yeni bir factory oluşturun.
class FormFactory
{
public function __construct(
private Translator $translator,
) {
}
public function create(): Form
{
$form = new Form;
$form->setTranslator($this->translator);
return $form;
}
}
// ✅ kompozisyon kullanımı
class EditFormFactory
{
public function __construct(
private FormFactory $formFactory,
) {
}
public function create(): Form
{
$form = $this->formFactory->create();
// diğer form alanları buraya eklenir
$form->addSubmit('send', 'Kaydet');
return $form;
}
}
Can alıcı nokta şu: FormFactory ile EditFormFactory sınıfları arasındaki ilişki nesne kalıtımıyla değil, kompozisyonla kurulmalıdır:
// ⛔ HAYIR! KALITIMIN BURADA YERİ YOK
class EditFormFactory extends FormFactory
{
public function create(): Form
{
$form = parent::create();
$form->addText('title', 'Başlık:');
// diğer form alanları buraya eklenir
$form->addSubmit('send', 'Kaydet');
return $form;
}
}
Burada kalıtım kullanmak tümüyle ters teperdi. Çok kısa sürede sorunlarla karşılaşırdınız. Örneğin
create() metoduna parametre eklemek isteseydiniz, imzası atanınkinden farklı olacağı için PHP hata verirdi. Ya
da EditFormFactory sınıfına yapıcı üzerinden bağımlılık aktarırken. Bu da constructor hell denen duruma
yol açardı.
Genel olarak kompozisyonu kalıtıma yeğlemek daha iyidir.
Formun İşlenmesi
Başarılı gönderim üzerine çağrılan form işleyicisi de factory sınıfının bir parçası olabilir. Çalışma
biçimi, gönderilen verileri işlenmek üzere model katmanına aktarmaktır. İşleme hataları forma geri aktarılır. Aşağıdaki örnekte modeli
Facade sınıfı temsil ediyor:
class EditFormFactory
{
public function __construct(
private FormFactory $formFactory,
private Facade $facade,
) {
}
public function create(): Form
{
$form = $this->formFactory->create();
$form->addText('title', 'Başlık:');
// diğer form alanları buraya eklenir
$form->addSubmit('send', 'Kaydet');
$form->onSuccess[] = $this->processForm(...);
return $form;
}
private function processForm(Form $form, array $data): void
{
try {
// gönderilen verilerin işlenmesi
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
}
}
}
Ancak yönlendirmeyi presenter'ın kendisi üstlensin. onSuccess olayına, yönlendirmeyi yapan başka bir
işleyici ekler. Böylece form çeşitli presenter'larda kullanılabilir ve her biri başarı durumunda farklı bir yere
yönlendirebilir.
class MyPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private EditFormFactory $formFactory,
) {
}
protected function createComponentEditForm(): Form
{
$form = $this->formFactory->create();
$form->onSuccess[] = function () {
$this->flashMessage('Kayıt kaydedildi');
$this->redirect('Homepage:');
};
return $form;
}
}
Bu çözüm, formların şu özelliğinden yararlanır: form üzerinde ya da öğelerinden birinde addError()
çağrılırsa, sonraki onSuccess işleyicileri çağrılmaz.
Form Sınıfından Türetme
Kurulmuş bir form, Form sınıfının torunu olmamalıdır. Başka bir deyişle, şu yaklaşımdan
kaçının:
// ⛔ HAYIR! KALITIMIN BURADA YERİ YOK
class EditForm extends Form
{
public function __construct(Translator $translator)
{
parent::__construct();
$this->addText('title', 'Başlık:');
// diğer form alanları buraya eklenir
$this->addSubmit('send', 'Kaydet');
$this->setTranslator($translator);
}
}
Formu yapıcının içinde kurmak yerine bir factory kullanın.
Şunu fark etmek önemlidir: Form sınıfı öncelikle form kurmaya yarayan bir araçtır, yani bir form
builder. Kurulmuş form ise onun ürünü sayılabilir. Ancak ürün, builder'ın özel bir türü değildir; aralarında
kalıtımın temeli olan bir is a ilişkisi yoktur.
Form Bileşeni
Tümüyle farklı bir yaklaşım, formu içine alan bir bileşen oluşturmaktır. Bu, yeni olanaklar açar; örneğin bileşenin kendi şablonu olduğundan formu belirli bir biçimde render edebilirsiniz. Ya da AJAX iletişimi ve forma dinamik olarak bilgi yüklemek için (örneğin öneriler vb.) sinyaller kullanılabilir.
use Nette\Application\UI\Form;
class EditControl extends Nette\Application\UI\Control
{
public array $onSave = [];
public function __construct(
private Facade $facade,
) {
}
protected function createComponentForm(): Form
{
$form = new Form;
$form->addText('title', 'Başlık:');
// diğer form alanları buraya eklenir
$form->addSubmit('send', 'Kaydet');
$form->onSuccess[] = $this->processForm(...);
return $form;
}
private function processForm(Form $form, array $data): void
{
try {
// gönderilen verilerin işlenmesi
$this->facade->process($data);
} catch (AnyModelException $e) {
$form->addError('...');
return;
}
// olayın çağrılması
$this->onSave($this, $data);
}
}
Sonra bu bileşeni üretecek bir factory oluşturalım. Arayüzünü tanımlamak yeterlidir:
interface EditControlFactory
{
function create(): EditControl;
}
Ve onu yapılandırma dosyasına ekleyin:
services:
- EditControlFactory
Artık factory'yi isteyip presenter'da kullanabiliriz:
class MyPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private EditControlFactory $controlFactory,
) {
}
protected function createComponentEditForm(): EditControl
{
$control = $this->controlFactory->create();
$control->onSave[] = function (EditControl $control, $data) {
$this->redirect('this');
// ya da düzenleme sonucuna yönlendir, örneğin:
// $this->redirect('detail', ['id' => $data->id]);
};
return $control;
}
}