İnteraktif Bileşenler

Bileşenler, sayfalara yerleştirdiğimiz ayrı yeniden kullanılabilir nesnelerdir. Bunlar formlar, datagridler, anketler, aslında tekrar tekrar kullanılması mantıklı olan her şey olabilir. Göstereceğiz:

  • bileşenler nasıl kullanılır?
  • nasıl yazılır?
  • sinyaller nedir?

Nette yerleşik bir bileşen sistemi vardır. Daha yaşlılarınız Delphi veya ASP.NET Web Forms'dan benzer bir şeyi hatırlayabilir. React veya Vue.js uzaktan benzer bir şey üzerine inşa edilmiştir. Ancak, PHP çerçeveleri dünyasında bu tamamen benzersiz bir özelliktir.

Aynı zamanda bileşenler, uygulama geliştirme yaklaşımını temelden değiştirir. Önceden hazırlanmış birimlerden sayfalar oluşturabilirsiniz. Yönetimde datagrid'e mi ihtiyacınız var? Nette için açık kaynaklı eklentilerin (sadece bileşenlerin değil) bir deposu olan Componette'de bulabilir ve basitçe sunucuya yapıştırabilirsiniz.

Sunucuya istediğiniz sayıda bileşen ekleyebilirsiniz. Ve bazı bileşenlerin içine başka bileşenler ekleyebilirsiniz. Bu, kök olarak sunum yapan bir bileşen ağacı oluşturur.

Fabrika Yöntemleri

Bileşenler sunucuya nasıl yerleştirilir ve daha sonra nasıl kullanılır? Genellikle fabrika yöntemleri kullanılarak.

Bileşen fabrikası, bileşenleri yalnızca gerçekten ihtiyaç duyulduklarında (tembel / isteğe bağlı) oluşturmanın zarif bir yoludur. Tüm sihir, aşağıdaki gibi adlandırılan bir yöntemin uygulanmasındadır createComponent<Name>(), nerede <Name> oluşturacak ve geri dönecek bileşenin adıdır.

class DefaultPresenter extends Nette\Application\UI\Presenter
{
	protected function createComponentPoll(): PollControl
	{
		$poll = new PollControl;
		$poll->items = $this->item;
		return $poll;
	}
}

Tüm bileşenler ayrı yöntemlerle oluşturulduğu için kod daha temizdir ve okunması daha kolaydır.

Bileşen adları her zaman küçük harfle başlar, ancak yöntem adında büyük harfle yazılır.

Fabrikaları asla doğrudan çağırmayız, bileşenleri ilk kez kullandığımızda otomatik olarak çağrılırlar. Bu sayede, bir bileşen doğru zamanda ve yalnızca gerçekten ihtiyaç duyulduğunda oluşturulur. Bileşeni kullanmayacaksak (örneğin sayfanın yalnızca bir kısmını döndürdüğümüz bazı AJAX isteklerinde veya parçalar önbelleğe alındığında), bileşen oluşturulmaz bile ve sunucunun performansından tasarruf ederiz.

// bileşene erişiyoruz ve eğer bu ilk sefer ise,
// oluşturmak için createComponentPoll() işlevini çağırır
$poll = $this->getComponent('poll');
// alternatif sözdizimi: $poll = $this['poll'];

Şablonda, {control} etiketini kullanarak bir bileşen oluşturabilirsiniz. Böylece bileşenleri şablona manuel olarak geçirmeye gerek kalmaz.

<h2>Please Vote</h2>

{control poll}

Hollywood Tarzı

Bileşenler genellikle Hollywood tarzı olarak adlandırdığımız harika bir teknik kullanır. Oyuncu seçmelerinde oyuncuların sık sık duyduğu klişeyi biliyorsunuzdur: “Siz bizi aramayın, biz sizi ararız.” İşte bu da bununla ilgili.

Nette, sürekli soru sormak zorunda kalmak yerine (“form gönderildi mi?”, “geçerli miydi?” veya “bu düğmeye basan oldu mu?”), framework'e “bu olduğunda, bu yöntemi çağır” der ve daha fazla çalışmayı bırakırsınız. 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 motor bunlara uygun parametreleri aktarır.

Bu, uygulama yazma şeklinizi tamamen değiştirir. Framework'e ne kadar çok görev devredebilirseniz, o kadar az işiniz olur. Ve o kadar az unutabilirsiniz.

Bir Bileşen Nasıl Yazılır

Bileşen derken genellikle Nette\Application\UI\Control sınıfının torunlarını kastediyoruz. Sunucu Nette\Application\UI\Presenter 'un kendisi de Control sınıfının bir torunudur.

use Nette\Application\UI\Control;

class PollControl extends Control
{
}

Rendering

{control componentName} etiketinin bir bileşeni çizmek için kullanıldığını zaten biliyoruz. Aslında render() bileşeninin render işlemini gerçekleştirdiğimiz metodunu çağırır. Tıpkı sunucuda olduğu gibi, parametreleri aktardığımız $this->template değişkeninde bir Latte şablonumuz var. Sunucudaki kullanımdan farklı olarak, bir şablon dosyası belirtmeli ve render edilmesine izin vermeliyiz:

public function render(): void
{
	// şablona bazı parametreler koyacağız
	$this->template->param = $value;
	// ve çiz
	$this->template->render(__DIR__ . '/poll.latte');
}

{control} etiketi, render() yöntemine parametre aktarılmasına olanak tanır:

{control poll $id, $message}
public function render(int $id, string $message): void
{
	// ...
}

Bazen bir bileşen, ayrı ayrı render etmek istediğimiz birkaç parçadan oluşabilir. Her biri için kendi render yöntemini oluşturacağız, örneğin renderPaginator():

public function renderPaginator(): void
{
	// ...
}

Ve şablonda daha sonra bunu kullanarak çağırıyoruz:

{control poll:paginator}

Daha iyi anlamak için etiketin PHP koduna nasıl derlendiğini bilmek iyi olacaktır.

{control poll}
{control poll:paginator 123, 'hello'}

Bu şu anlama geliyor:

$control->getComponent('poll')->render();
$control->getComponent('poll')->renderPaginator(123, 'hello');

getComponent() yöntemi poll bileşenini döndürür ve ardından bu bileşen üzerinde sırasıyla render() veya renderPaginator() yöntemi çağrılır.

Parametre kısmının herhangi bir yerinde => kullanılırsa, tüm parametreler bir dizi ile sarılır ve ilk bağımsız değişken olarak geçirilir:

{control poll, id: 123, message: 'hello'}

derlenir:

$control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']);

Alt bileşenin render edilmesi:

{control cartControl-someForm}

derlenir:

$control->getComponent("cartControl-someForm")->render();

Sunucular gibi bileşenler de şablonlara otomatik olarak çeşitli yararlı değişkenler aktarır:

  • $basePath kök dizine giden mutlak bir URL yoludur (örneğin /CD-collection)
  • $baseUrl kök dizine mutlak bir URL'dir (örneğin http://localhost/CD-collection)
  • $user kullanıcıyı temsil eden bir nesnedir
  • $presenter şu anki sunucu
  • $control mevcut bileşendir
  • $flashes yöntem tarafından gönderilen mesajların listesi flashMessage()

Sinyal

Nette uygulamasında gezinmenin Presenter:action çiftlerine bağlantı vermek veya yönlendirmekten oluştuğunu zaten biliyoruz. Peki ya sadece mevcut sayfa üzerinde bir eylem gerçekleştirmek istiyorsak? Örneğin, tablodaki sütunun sıralama düzenini değiştirmek; öğeyi silmek; aydınlık/karanlık modunu değiştirmek; formu göndermek; ankette oy kullanmak vb.

Bu tür isteklere sinyal adı verilir. Ve eylemlerin yöntemleri çağırması gibi action<Action>() veya render<Action>(), sinyaller yöntemleri çağırır handle<Signal>(). Eylem (veya görünüm) kavramı yalnızca sunum yapanlarla ilgiliyken, sinyaller tüm bileşenler için geçerlidir. Ve dolayısıyla sunucular için de geçerlidir, çünkü UI\Presenter, UI\Control'un soyundan gelmektedir.

public function handleClick(int $x, int $y): void
{
	// ... sinyalin işlenmesi ...
}

Sinyali çağıran bağlantı olağan şekilde oluşturulur, yani şablonda n:href niteliği veya {link} etiketi ile, kodda link() yöntemi ile. Daha fazlası URL bağlantıları oluşturma bölümünde.

<a n:href="click! $x, $y">click here</a>

Sinyal her zaman geçerli sunumcu ve görünümde çağrılır, bu nedenle farklı sunumcu / eylemde sinyale bağlanmak mümkün değildir.

Böylece sinyal, sayfanın orijinal istekle tamamen aynı şekilde yeniden yüklenmesine neden olur, sadece ek olarak sinyal işleme yöntemini uygun parametrelerle çağırır. Yöntem mevcut değilse, kullanıcıya 403 Forbidden hata sayfası olarak görüntülenen Nette\Application\UI\BadSignalException istisnası atılır.

Snippet'ler ve AJAX

Sinyaller size biraz AJAX'ı hatırlatabilir: mevcut sayfada çağrılan işleyiciler. Ve haklısınız, sinyaller genellikle AJAX kullanılarak çağrılır ve daha sonra sayfanın yalnızca değişen kısımlarını tarayıcıya iletiriz. Bunlara snippet adı verilir. AJAX ile ilgili sayfada daha fazla bilgi bulabilirsiniz.

Flaş Mesajlar

Bir bileşen, sunum yapan kişiden bağımsız olarak kendi flaş mesaj deposuna sahiptir. Bunlar, örneğin işlemin sonucu hakkında bilgi veren mesajlardır. Flaş mesajların önemli bir özelliği, yeniden yönlendirmeden sonra bile şablonda mevcut olmalarıdır. Görüntülendikten sonra bile 30 saniye daha canlı kalırlar – örneğin, kullanıcının istemeden sayfayı yenilemesi durumunda – mesaj kaybolmaz.

Gönderme işlemi flashMessage yöntemi ile yapılır. İlk parametre mesaj metni veya mesajı temsil eden stdClass nesnesidir. İsteğe bağlı ikinci parametre mesajın türüdür (hata, uyarı, bilgi, vb.). flashMessage() yöntemi, bilgi aktarabileceğiniz stdClass nesnesi olarak bir flash message örneği döndürür.

$this->flashMessage('Item was deleted.');
$this->redirect(/* ... */); // ve yeniden yönlendir

Şablonda, bu mesajlar $flashes değişkeninde message (mesaj metni), type (mesaj türü) özelliklerini içeren ve daha önce bahsedilen kullanıcı bilgilerini içerebilen stdClass nesneleri olarak mevcuttur. Bunları aşağıdaki gibi çiziyoruz:

{foreach $flashes as $flash}
	<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}

Kalıcı Parametreler

Kalıcı parametreler, farklı istekler arasında bileşenlerdeki durumu korumak için kullanılır. Bir bağlantı tıklandıktan sonra bile değerleri aynı kalır. Oturum verilerinin aksine, URL içinde aktarılırlar. Ve aynı sayfadaki diğer bileşenlerde oluşturulan bağlantılar da dahil olmak üzere otomatik olarak aktarılırlar.

Örneğin, bir içerik sayfalama bileşeniniz var. Bir sayfada bu tür birkaç bileşen olabilir. Ve bağlantıya tıkladığınızda tüm bileşenlerin geçerli sayfalarında kalmasını istiyorsunuz. Bu nedenle, sayfa numarasını (page) kalıcı bir parametre haline getiriyoruz.

Kalıcı bir parametre oluşturmak Nette son derece kolaydır. Sadece bir public özellik oluşturun ve şu nitelikle etiketleyin: (daha önce /** @persistent */ kullanılıyordu)

use Nette\Application\Attributes\Persistent; // bu satır önemlidir

class PaginatingControl extends Control
{
	#[Persistent]
	public int $page = 1; // halka açık olmalı
}

Veri türünü (örn. int) özellikle birlikte eklemenizi öneririz ve ayrıca varsayılan bir değer de ekleyebilirsiniz. Parametre değerleri doğrulanabilir.

Bir bağlantı oluştururken kalıcı bir parametrenin değerini değiştirebilirsiniz:

<a n:href="this page: $page + 1">next</a>

Ya da reset edilebilir, yani URL'den kaldırılabilir. Daha sonra varsayılan değerini alacaktır:

<a n:href="this page: null">reset</a>

Kalıcı Bileşenler

Sadece parametreler değil, bileşenler de kalıcı olabilir. Kalıcı parametreleri de farklı eylemler arasında veya farklı sunucular arasında aktarılır. Kalıcı bileşenleri sunum yapan sınıf için bu ek açıklamalarla işaretleriz. Örneğin burada calendar ve poll bileşenlerini aşağıdaki gibi işaretliyoruz:

/**
 * @persistent(calendar, poll)
 */
class DefaultPresenter extends Nette\Application\UI\Presenter
{
}

Alt bileşenleri kalıcı olarak işaretlemenize gerek yoktur, otomatik olarak kalıcı olurlar.

PHP 8'de, kalıcı bileşenleri işaretlemek için öznitelikleri de kullanabilirsiniz:

use Nette\Application\Attributes\Persistent;

#[Persistent('calendar', 'poll')]
class DefaultPresenter extends Nette\Application\UI\Presenter
{
}

Bağımlılıkları Olan Bileşenler

Bağımlılıkları olan bileşenleri, bunları kullanacak sunumcuları “karıştırmadan” nasıl oluşturabiliriz? Nette'deki DI konteynerinin akıllı özellikleri sayesinde, geleneksel servisleri kullanırken olduğu gibi, işin çoğunu çerçeveye bırakabiliriz.

Örnek olarak PollFacade hizmetine bağımlılığı olan bir bileşeni ele alalım:

class PollControl extends Control
{
	public function __construct(
		private int $id, // Bileşenin oluşturulduğu anketin kimliği
		private PollFacade $facade,
	) {
	}

	public function handleVote(int $voteId): void
	{
		$this->facade->vote($id, $voteId);
		// ...
	}
}

Klasik bir hizmet yazıyor olsaydık, endişelenecek bir şey olmazdı. DI konteyneri görünmez bir şekilde tüm bağımlılıkları aktarmakla ilgilenirdi. Ancak bileşenleri genellikle fabrika yöntemleriyle doğrudan sunucuda yeni bir örneğini oluşturarak ele alırız createComponent...(). Ancak tüm bileşenlerin tüm bağımlılıklarını sunucuya aktarmak ve daha sonra bunları bileşenlere aktarmak zahmetlidir. Ve yazılan kod miktarı…

Mantıklı soru şu: Neden bileşeni klasik bir hizmet olarak kaydetmiyor, sunucuya aktarmıyor ve ardından createComponent...() yönteminde döndürmüyoruz? Ancak bu yaklaşım uygun değildir çünkü bileşeni birden çok kez oluşturabilmek istiyoruz.

Doğru çözüm, bileşen için bir fabrika, yani bileşeni bizim için oluşturan bir sınıf yazmaktır:

class PollControlFactory
{
	public function __construct(
		private PollFacade $facade,
	) {
	}

	public function create(int $id): PollControl
	{
		return new PollControl($id, $this->facade);
	}
}

Şimdi servisimizi DI konteynerine yapılandırma için kaydediyoruz:

services:
	- PollControlFactory

Son olarak, bu fabrikayı sunucumuzda kullanacağız:

class PollPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private PollControlFactory $pollControlFactory,
	) {
	}

	protected function createComponentPollControl(): PollControl
	{
		$pollId = 1; // parametremizi geçebiliriz
		return $this->pollControlFactory->create($pollId);
	}
}

İşin en güzel yanı, Nette DI'nın bu kadar basit fabrikalar üretebilmesidir, bu nedenle tüm kodu yazmak yerine sadece arayüzünü yazmanız yeterlidir:

interface PollControlFactory
{
	public function create(int $id): PollControl;
}

Hepsi bu kadar. Nette bu arayüzü dahili olarak uygular ve onu kullanabileceğimiz sunucumuza enjekte eder. Ayrıca $id parametremizi ve PollFacade sınıfının örneğini sihirli bir şekilde bileşenimize aktarır.

Derinlemesine Bileşenler

Bir Nette Uygulamasındaki bileşenler, bu bölümün konusu olan sayfalara gömdüğümüz bir web uygulamasının yeniden kullanılabilir parçalarıdır. Böyle bir bileşenin yetenekleri tam olarak nelerdir?

  1. bir şablon içinde oluşturulabilir
  2. AJAX isteği sırasında hangi bölümünün işleneceğini bilir (snippet'ler)
  3. durumunu bir URL'de saklama yeteneğine sahiptir (kalıcı parametreler)
  4. kullanıcı eylemlerine (sinyallere) yanıt verme yeteneğine sahiptir
  5. hiyerarşik bir yapı oluşturur (kökün sunum yapan kişi olduğu)

Bu işlevlerin her biri kalıtım soyu sınıflarından biri tarafından yerine getirilir. Oluşturma (1 + 2) Nette\Application\UI\Control tarafından, yaşam döngüsüne dahil etme (3, 4) Nette\Application\UI\Component sınıfı tarafından ve hiyerarşik yapının oluşturulması (5) Container ve Component sınıfları tarafından gerçekleştirilir.

Nette\ComponentModel\Component  { IComponent }
|
+- Nette\ComponentModel\Container  { IContainer }
	|
	+- Nette\Application\UI\Component  { SignalReceiver, StatePersistent }
		|
		+- Nette\Application\UI\Control  { Renderable }
			|
			+- Nette\Application\UI\Presenter  { IPresenter }

Bileşenin Yaşam Döngüsü

Bileşenin yaşam döngüsü

Kalıcı Parametrelerin Doğrulanması

URL'lerden alınan kalıcı parametrelerin değerleri loadState() metodu tarafından özelliklere yazılır. Ayrıca, özellik için belirtilen veri türünün eşleşip eşleşmediğini kontrol eder, aksi takdirde 404 hatasıyla yanıt verir ve sayfa görüntülenmez.

Kalıcı parametrelere asla körü körüne güvenmeyin çünkü bunlar URL'de kullanıcı tarafından kolayca üzerine yazılabilir. Örneğin, $this->page sayfa numarasının 0'dan büyük olup olmadığını bu şekilde kontrol ederiz. Bunu yapmanın iyi bir yolu, yukarıda bahsedilen loadState() yöntemini geçersiz kılmaktır:

class PaginatingControl extends Control
{
	#[Persistent]
	public int $page = 1;

	public function loadState(array $params): void
	{
		parent::loadState($params); // burada $this->page ayarlanır
		// kullanıcı değeri kontrolünü takip eder:
		if ($this->page < 1) {
			$this->error();
		}
	}
}

Tersi işlem, yani kalıcı özelliklerden değerlerin toplanması, saveState() yöntemi tarafından gerçekleştirilir.

Derinlemesine Sinyaller

Bir sinyal, orijinal istek gibi bir sayfanın yeniden yüklenmesine neden olur (AJAX hariç) ve Nette\Application\UI\Component sınıfındaki varsayılan uygulaması handle{Signal} sözcüklerinden oluşan bir yöntemi çağırmaya çalışan signalReceived($signal) yöntemini çağırır. Daha sonraki işlemler verilen nesneye dayanır. Component 'un torunları olan nesneler (yani Control ve Presenter) handle{Signal} 'u ilgili parametrelerle çağırmaya çalışır.

Başka bir deyişle: handle{Signal} metodunun tanımı alınır ve istekte alınan tüm parametreler metodun parametreleriyle eşleştirilir. Yani URL'den gelen id parametresi metodun $id parametresiyle, something parametresi $something parametresiyle eşleştirilir ve bu böyle devam eder. Ve eğer metot mevcut değilse, signalReceived metodu bir istisna atar.

Sinyal, bileşen ağacına bağlıysa SignalReceiver arayüzünü uygulayan herhangi bir bileşen, nesne sunucusu tarafından alınabilir.

Sinyallerin ana alıcıları Presenters ve Control adresini genişleten görsel bileşenlerdir. Sinyal, bir nesne için bir şey yapması gerektiğine dair bir işarettir – anket kullanıcıdan gelen bir oyu sayar, haber kutusu açılmalıdır, form gönderilmiştir ve verileri işlemelidir vb.

Sinyal için URL Component::link() yöntemi kullanılarak oluşturulur. $destination parametresi olarak {signal}! dizesini ve $args olarak sinyal işleyicisine iletmek istediğimiz argümanların bir dizisini iletiriz. Sinyal parametreleri geçerli sunucunun/görüntünün URL'sine eklenir. URL'deki ?do parametresi çağrılan sinyali belirler.

Biçimi {signal} veya {signalReceiver}-{signal} şeklindedir. {signalReceiver}, sunum programındaki bileşenin adıdır. Bu nedenle bileşenlerin adında kısa çizgi (yanlış olarak tire) bulunamaz – bileşenin adını ve sinyali bölmek için kullanılır, ancak birkaç bileşen oluşturmak mümkündür.

isSignalReceiver() yöntemi, bir bileşenin (ilk bağımsız değişken) bir sinyalin (ikinci bağımsız değişken) alıcısı olup olmadığını doğrular. İkinci bağımsız değişken atlanabilir – o zaman bileşenin herhangi bir sinyalin alıcısı olup olmadığını bulur. İkinci parametre true ise, bileşenin veya torunlarının bir sinyalin alıcısı olup olmadığını doğrular.

handle{Signal} adresinden önceki herhangi bir aşamada, sinyal yürütme sorumluluğunu üstlenen processSignal() yöntemi çağrılarak manuel olarak sinyal gerçekleştirilebilir. Alıcı bileşeni alır (ayarlanmamışsa sunucunun kendisidir) ve sinyali gönderir.

Örnek:

if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, 'sorting')) {
	$this->processSignal();
}

Sinyal zamanından önce yürütülür ve bir daha çağrılmaz.

versiyon: 4.0