İ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ğinhttp://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 listesiflashMessage()
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?
- bir şablon içinde oluşturulabilir
- AJAX isteği sırasında hangi bölümünün işleneceğini bilir (snippet'ler)
- durumunu bir URL'de saklama yeteneğine sahiptir (kalıcı parametreler)
- kullanıcı eylemlerine (sinyallere) yanıt verme yeteneğine sahiptir
- 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.