Formları Birden Fazla Yerde Yeniden Kullanma

Nette, aynı formu kod kopyalamadan birden fazla yerde yeniden kullanmak için çeşitli seçeneklere sahipsiniz. Bu makalede, kaçınmanız gerekenler de dahil olmak üzere farklı çözümlerin üzerinden geçeceğiz.

Form Fabrikası

Aynı bileşeni birden fazla yerde kullanmaya yönelik temel yaklaşımlardan biri, bileşeni oluşturan bir yöntem veya sınıf oluşturmak ve daha sonra bu yöntemi uygulamanın farklı yerlerinde çağırmaktır. Böyle bir yöntem veya sınıf factory olarak adlandırılır. Lütfen fabrikaları kullanmanın belirli bir yolunu açıklayan ve bu konuyla ilgili olmayan factory method tasarım modeliyle karıştırmayın.

Örnek olarak, bir düzenleme formu oluşturacak bir fabrika oluşturalım:

use Nette\Application\UI\Form;

class FormFactory
{
	public function createEditForm(): Form
	{
		$form = new Form;
		$form->addText('title', 'Title:');
		// ek form alanları buraya eklenir
		$form->addSubmit('send', 'Save');
		return $form;
	}
}

Artık bu fabrikayı uygulamanızın farklı yerlerinde, örneğin sunumlarda veya bileşenlerde kullanabilirsiniz. Ve bunu bir bağımlılık olarak talep ederek yapıyoruz. Bu yüzden önce sınıfı yapılandırma dosyasına yazacağız:

services:
	- FormFactory

Ve sonra bunu sunumda kullanıyoruz:

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;
	}
}

Uygulamanıza uygun diğer form türlerini oluşturmak için form fabrikasını ek yöntemlerle genişletebilirsiniz. Ve elbette, diğer yöntemlerin kullanacağı öğeler olmadan temel bir form oluşturan bir yöntem ekleyebilirsiniz:

class FormFactory
{
	public function createForm(): Form
	{
		$form = new Form;
		return $form;
	}

	public function createEditForm(): Form
	{
		$form = $this->createForm();
		$form->addText('title', 'Title:');
		// ek form alanları buraya eklenir
		$form->addSubmit('send', 'Save');
		return $form;
	}
}

createForm() yöntemi 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 formlar için bir çevirmen ayarlamamız gerektiği anlamına gelir. Bunu yapmak için FormFactory sınıfını, Translator nesnesini kurucuda bir bağımlılık olarak kabul edecek ve forma aktaracak şekilde değiştiriyoruz:

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() yöntemi, belirli formları oluşturan diğer yöntemler tarafından da çağrıldığından, çevirmeni yalnızca bu yöntemde ayarlamamız gerekir. Ve işimiz bitti. Herhangi bir sunumcu veya bileşen kodunu değiştirmeye gerek yok, ki bu harika.

Daha 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 kodun okunabilirliğini artırabilir ve formların yönetimini kolaylaştırabilir. Temel yapılandırmaya sahip (örneğin çeviri destekli) saf bir form oluşturmak için orijinal FormFactory adresini bırakın ve düzenleme formu için yeni bir fabrika EditFormFactory 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();
		// ek form alanları buraya eklenir
		$form->addSubmit('send', 'Save');
		return $form;
	}
}

FormFactory ve EditFormFactory sınıfları arasındaki bağın nesne kalıtımı ile değil, bileşim ile uygulanması çok önemlidir:

// ⛔ HAYIR! MİRAS BURAYA AİT DEĞİL
class EditFormFactory extends FormFactory
{
	public function create(): Form
	{
		$form = parent::create();
		$form->addText('title', 'Title:');
		// ek form alanları buraya eklenir
		$form->addSubmit('send', 'Save');
		return $form;
	}
}

Bu durumda kalıtım kullanmak tamamen ters etki yaratacaktır. Çok hızlı bir şekilde sorunlarla karşılaşırsınız. Örneğin, create() yöntemine parametre eklemek isterseniz; PHP, imzasının ebeveynden farklı olduğuna dair bir hata bildirecektir. Ya da EditFormFactory sınıfına yapıcı aracılığıyla bir bağımlılık aktarırken. Bu, yapıcı cehennemi dediğimiz şeye neden olur.

Genellikle kalıtım yerine bileşimi tercih etmek daha iyidir.

Form İşleme

Başarılı bir gönderim sonrasında çağrılan form işleyici de bir fabrika sınıfının parçası olabilir. Gönderilen verileri işlenmek üzere modele aktararak çalışacaktır. Herhangi bir hatayı forma geri iletecektir. Aşağıdaki örnekteki 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', 'Title:');
		// ek form alanları buraya eklenir
		$form->addSubmit('send', 'Save');
		$form->onSuccess[] = [$this, 'processForm'];
		return $form;
	}

	public function processForm(Form $form, array $data): void
	{
		try {
			// sunulan veri̇leri̇n i̇şlenmesi̇
			$this->facade->process($data);

		} catch (AnyModelException $e) {
			$form->addError('...');
		}
	}
}

Sunucunun yeniden yönlendirmeyi kendisinin yapmasına izin verin. Yeniden yönlendirmeyi gerçekleştirecek olan onSuccess olayına başka bir işleyici ekleyecektir. Bu, formun farklı sunumcularda kullanılmasına olanak tanıyacak ve her biri farklı bir konuma yönlendirebilecektir.

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('Záznam byl uložen');
			$this->redirect('Homepage:');
		};
		return $form;
	}
}

Bu çözüm, bir form veya öğesi üzerinde addError() çağrıldığında, bir sonraki onSuccess işleyicisinin çağrılmaması şeklindeki form özelliğinden yararlanır.

Form Sınıfından Devralma

Oluşturulan bir formun bir formun çocuğu olmaması gerekir. Başka bir deyişle, bu çözümü kullanmayın:

// ⛔ HAYIR! MİRAS BURAYA AİT DEĞİL
class EditForm extends Form
{
	public function __construct(Translator $translator)
	{
		parent::__construct();
		$form->addText('title', 'Title:');
		// ek form alanları buraya eklenir
		$form->addSubmit('send', 'Save');
		$form->setTranslator($translator);
	}
}

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

Form sınıfının öncelikle bir formu bir araya getirmek için bir araç, yani bir form oluşturucu olduğunu anlamak önemlidir. Ve birleştirilmiş form onun ü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 is a ilişkisi yoktur.

Form Bileşeni

Tamamen farklı bir yaklaşım, bir form içeren bir bileşen oluşturmaktır. Bu, örneğin bileşen bir şablon içerdiğinden formu belirli bir şekilde oluşturmak gibi yeni olanaklar sağlar. Veya sinyaller AJAX iletişimi ve forma bilgi yüklemek için kullanılabilir, örneğin ipucu vb. için.

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', 'Title:');
		// ek form alanları buraya eklenir
		$form->addSubmit('send', 'Save');
		$form->onSuccess[] = [$this, 'processForm'];

		return $form;
	}

	public function processForm(Form $form, array $data): void
	{
		try {
			// sunulan veri̇leri̇n i̇şlenmesi̇
			$this->facade->process($data);

		} catch (AnyModelException $e) {
			$form->addError('...');
			return;
		}

		// olay çağrısı
		$this->onSave($this, $data);
	}
}

Bu bileşeni üretecek bir fabrika oluşturalım. Arayüzünü yazmak yeterli:

interface EditControlFactory
{
	function create(): EditControl;
}

Ve yapılandırma dosyasına ekleyin:

services:
	- EditControlFactory

Ve şimdi fabrikayı talep edebilir ve sunumda 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önlendirin, örn:
			// $this->redirect('detail', ['id' => $data->id]);
		};

		return $control;
	}
}