Formların Birden Fazla Yerde Yeniden Kullanımı

Nette'de, aynı formu birden fazla yerde kullanmak ve kodu tekrarlamamak için çeşitli seçenekleriniz vardır. Bu makalede, kaçınmanız gerekenler de dahil olmak üzere farklı çözümleri göstereceğiz.

Form Fabrikası

Aynı bileşeni birden fazla yerde kullanmanın temel yaklaşımlarından biri, bu bileşeni üreten bir metot veya sınıf oluşturmak ve ardından bu metodu uygulamanın farklı yerlerinde çağırmaktır. Böyle bir metoda veya sınıfa fabrika denir. Lütfen fabrikaların belirli bir kullanım şeklini tanımlayan ve bu konuyla ilgili olmayan factory method tasarım deseniyle karıştırmayın.

Örnek olarak, bir düzenleme formu oluşturacak bir fabrika yaratacağız:

use Nette\Application\UI\Form;

class FormFactory
{
	public function createEditForm(): Form
	{
		$form = new Form;
		$form->addText('title', 'Başlık:');
		// buraya diğer form alanları eklenir
		$form->addSubmit('send', 'Gönder');
		return $form;
	}
}

Şimdi bu fabrikayı uygulamanızın farklı yerlerinde, örneğin presenter'larda veya bileşenlerde kullanabilirsiniz. Bunu bağımlılık olarak talep edeceğiz. Önce sınıfı yapılandırma dosyasına yazarız:

services:
	- FormFactory

Ve sonra onu presenter'da kullanırız:

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 fabrikasını, uygulamanızın ihtiyaçlarına göre diğer tür formları oluşturmak için ek metotlarla genişletebilirsiniz. Ve tabii ki, öğeler olmadan temel bir form oluşturan ve diğer metotların kullanacağı 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:');
		// buraya diğer form alanları eklenir
		$form->addSubmit('send', 'Gönder');
		return $form;
	}
}

createForm() metodu henüz yararlı bir şey yapmıyor, ancak bu hızla değişecek.

Fabrika Bağımlılıkları

Zamanla, formların çok dilli olması gerektiği ortaya çıkacaktır. Bu, tüm formlara sözde çevirmeni ayarlamamız gerektiği anlamına gelir. Bu amaçla, FormFactory sınıfını yapıcıda Translator nesnesini bir bağımlılık olarak kabul edecek şekilde değiştiririz ve onu forma iletiriz:

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 diğer belirli formları oluşturan metotlar tarafından da çağrıldığı için, çevirmeni yalnızca bu metotta ayarlamak yeterlidir. Ve işimiz bitti. Herhangi bir presenter veya bileşenin kodunu değiştirmeye gerek yok, ki bu harika.

Birden Fazla Fabrika Sınıfı

Alternatif olarak, uygulamanızda kullanmak istediğiniz her form için birden fazla sınıf oluşturabilirsiniz. Bu yaklaşım, kod okunabilirliğini artırabilir ve form yönetimini kolaylaştırabilir. Orijinal FormFactory'yi yalnızca temel yapılandırmaya sahip (örneğin çeviri desteği ile) temiz bir form oluşturmak için bırakırız ve düzenleme formu için yeni bir EditFormFactory fabrikası oluştururuz.

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();
		// buraya diğer form alanları eklenir
		$form->addSubmit('send', 'Gönder');
		return $form;
	}
}

FormFactory ve EditFormFactory sınıfları arasındaki bağın nesne kalıtımı yerine kompozisyon ile gerçekleştirilmesi çok önemlidir:

// ⛔ BU ŞEKİLDE DEĞİL! KALITIM BURAYA AİT DEĞİL
class EditFormFactory extends FormFactory
{
	public function create(): Form
	{
		$form = parent::create();
		$form->addText('title', 'Başlık:');
		// buraya diğer form alanları eklenir
		$form->addSubmit('send', 'Gönder');
		return $form;
	}
}

Bu durumda kalıtım kullanmak tamamen verimsiz olurdu. Çok hızlı bir şekilde sorunlarla karşılaşırdınız. Örneğin, create() metoduna parametreler eklemek istediğinizde; PHP, imzasının ebeveyninden farklı olduğuna dair bir hata bildirirdi. Veya EditFormFactory sınıfına yapıcı aracılığıyla bir bağımlılık iletirken. Yapıcı cehennemi dediğimiz bir durum ortaya çıkardı.

Genel olarak, kalıtım yerine kompozisyonu tercih etmek daha iyidir.

Form İşleme

Başarılı bir gönderimden sonra çağrılan form işleyicisi de fabrika sınıfının bir parçası olabilir. Gönderilen verileri işlenmek üzere modele ileterek çalışacaktır. Olası hataları forma geri iletir. Aşağıdaki örnekte model, Facade sınıfı tarafından temsil edilmektedir:

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:');
		// buraya diğer form alanları eklenir
		$form->addSubmit('send', 'Gönder');
		$form->onSuccess[] = [$this, 'processForm'];
		return $form;
	}

	public 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'a bırakacağız. onSuccess olayına yönlendirmeyi gerçekleştirecek başka bir işleyici ekleyecektir. Bu sayede formu farklı presenter'larda kullanmak ve her birinde farklı bir yere yönlendirmek mümkün olacaktır.

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, form veya öğesi üzerinde addError() çağrıldığında sonraki onSuccess işleyicisinin çağrılmaması özelliğini kullanır.

Form Sınıfından Kalıtım Alma

Oluşturulan form, formun bir alt sınıfı olmamalıdır. Başka bir deyişle, bu çözümü kullanmayın:

// ⛔ BU ŞEKİLDE DEĞİL! KALITIM BURAYA AİT DEĞİL
class EditForm extends Form
{
	public function __construct(Translator $translator)
	{
		parent::__construct();
		$form->addText('title', 'Başlık:');
		// buraya diğer form alanları eklenir
		$form->addSubmit('send', 'Gönder');
		$form->setTranslator($translator);
	}
}

Formu yapıcıda oluşturmak yerine bir fabrika kullanın.

Form sınıfının öncelikle bir form oluşturma aracı, yani bir form oluşturucu olduğu unutulmamalıdır. Ve oluşturulan form, onun bir ürünü olarak düşünülebilir. Ancak ürün, oluşturucunun özel bir durumu değildir, aralarında kalıtımın temelini oluşturan bir is a ilişkisi yoktur.

Form İçeren Bileşen

Tamamen farklı bir yaklaşım, form içeren bir bileşen oluşturmaktır. Bu, örneğin formu belirli bir şekilde oluşturmak gibi yeni olanaklar sunar, çünkü bileşenin bir parçası olarak bir şablon da bulunur. Veya AJAX iletişimi ve forma bilgi yükleme, örneğin öneriler için sinyaller kullanılabilir, vb.

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:');
		// buraya diğer form alanları eklenir
		$form->addSubmit('send', 'Gönder');
		$form->onSuccess[] = [$this, 'processForm'];

		return $form;
	}

	public 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 tetiklenmesi
		$this->onSave($this, $data);
	}
}

Bu bileşeni üretecek bir fabrika da oluşturacağız. Sadece arayüzünü yazmanız yeterlidir:

interface EditControlFactory
{
	function create(): EditControl;
}

Ve yapılandırma dosyasına ekleyin:

services:
	- EditControlFactory

Ve şimdi fabrikayı talep edebilir ve 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');
			// veya düzenleme sonucuna yönlendiririz, örn.:
			// $this->redirect('detail', ['id' => $data->id]);
		};

		return $control;
	}
}