Tema Kancaları ve Çıktı Filtreleri

1.7k görüntülenme Markdown

Tema, aldığı veriyi ve ürettiği markup'ı kendi dosyasından değiştirir. Hiçbir controller ya da çekirdek şablonu düzenlenmez.

Genel Bakış

Her tema bir hooks.php taşıyabilir. Dosya, ilk sayfa oluşmadan önce bir kez include edilir; temayla taşınır ve temayla silinir.

Veri dinleyicileri view'a ulaşanı değiştirir: bu temanın her sayfada ihtiyaç duyduğu bir değişken, bir menü ağacı, bitmiş HTML. Markup noktaları ters yönde çalışır: tema onları açar, modül de içine enjekte eder.

Yapı

Nerede Durur

templates/website/{Tema}/hooks.php İsteğe bağlı. Düz PHP; sınıf yok, dönüş değeri yok. Dinleyicileri ve temanın yardımcı fonksiyonlarını tanımlar.
Theme::boot() Dosyayı istek başına bir kez, ilk sayfa oluşmadan önce include eder. View katmanı ve bağlamsız istek-dışı yol çağırır.
Kapsam Sadece website istekleri. Admin paneli ve zamanlanmış görevler tema çözmez.
Maliyet Her dinleyici her sayfa görüntülemesinde çalışır.

İki Aile

AileTema şudurMekanizmaDönüş
Veri (filtre)dinleyicikanca dosyasında Hook::add()Dönüşle değişir ya da referansla değiştirilir
Markup (enjeksiyon)yayıncıyerleşim ve parçalarda {hook name='...'}Dizeler birleştirilir, ham gösterilir

Referans

Kanca API'si

imzalar
// $priority: küçük olan önce çalışır; çakışma artırılarak çözülür, dinleyici asla düşürülmez.
// $properties: bir closure ya da ['class' => 'X', 'method' => 'y'] ya da ['class' => 'X', 'method::static' => 'y'].
public static function add($name, $priority, $properties = []): void;

// Değer kopyasıyla. Dinleyici başına bir giriş döner; markup noktası bunları birleştirir.
public static function run($name, ...$args): array;

// Referansla. HER argüman referansla geçer, bu yüzden her biri düz bir değişken olmalıdır.
public static function runRefs($name, &...$args): array;

Değişken Filtresi

Bir temanın her sayfaya değişken eklediği tek nokta. Her şablon için çalışır; bir kısmını hedefleyen dinleyici yolu sınar.

filter:template.variables View katmanında, veri seti tamamlandıktan sonra, şablon include edilmeden önce çalışır.
($template, $data) İşlenen şablonun tam yolu ve değişken dizisi. İkisi de referansla geçmez.
Dönüş Dizi dönerse veri seti tamamen ONUNLA DEĞİŞTİRİLİR. Başka her şey yok sayılır, set olduğu gibi kalır.
Tema başına tek dinleyici Sadece SON dönen dizi yaşar; temanın eklediği her şey tek gövdeye aittir.

Çıktı Filtresi

Motor işini bitirdikten sonra, tarayıcıya ulaşmadan önceki bitmiş HTML. Her yükü tek tek değiştirecek yeniden yazım burada yapılır.

filter:client.page.output View katmanının tema dalında, hem gösterilen hem döndürülen yolda çalışır.
(&$output, $view, $engine) Tüm HTML referansla, onu üreten view adı ve aktif motor. Yerinde değiştirilir; dönüş kullanılmaz.
Kapsam Sadece etiket motorları. Düz PHP motorunu bildiren tema çıktıyı include ile gösterir, burada yakalanmaz.
Parçalar da geçer AJAX ile cevaplanan bölüm parçaları aynı daldan geçer; $view ile ayırt edin.

Markup Noktaları

Yerleşimde {hook name='ui:client.head.css'} yazılır. Kurulu temalar yüz elliden fazlasını açar; taşınması gerekenler aşağıdadır.

NoktaYerleşimdeki konumuOraya ne aitDönüş
ui:client.head.csshead sonuStil bağlantıları, stil bloklarıSadece <link> ya da <style> döndürün; görünür markup girmez
ui:client.head.jshead sonuTemanın betiğinin kullandığı kütüphaneSadece <script> döndürün; satır içi ya da src'li
ui:client.body.beginbody başıEtiket yöneticisi çerçeveleri, üst bantlarHerhangi bir HTML dizesi döndürün
ui:client.body.endbody sonuErtelenmiş betikler, sohbet bileşenleri, diyaloglarHerhangi bir HTML dizesi döndürün
ui:client.nav.itemsÜst menü sonuEk bir üst seviye öğe<li> döndürün, çıplak <a> değil
ui:client.header.actionsÜst bant aksiyon kümesiSepetin yanına bir ikon butonu.btn.btn-soft.header-icon-btn taşıyan <a> ya da <button> döndürün
ui:client.user_menu.itemsHesap açılır menüsüGiriş yapılmış menüde bir satır.dropdown-item taşıyan <a> döndürün
ui:client.drawer.itemsMobil çekmeceMenü öğesinin mobil ikizi.drawer-link taşıyan çıplak <a> döndürün; çevresinde <li> olmaz
ui:client.content.topSayfa içeriği üstüSite geneli bir duyuru şeridiHerhangi bir HTML dizesi döndürün
ui:client.footer.columnsAltbilgi kolonlarından sonraEk bir bağlantı kolonuTek grid kolonu <div> döndürün; kardeşlerle aynı yapı, <ul>/<li> sarmalayıcı yok
ui:client.footer.bottomAltbilgi alt şeridiBir rozet, bir sözleşme satırıHerhangi bir HTML dizesi döndürün

Diğer Kancalar

KancaNe zamanTema bununla ne yapar
filter:client.themeTema adı çözülürkenHost, segment ya da önizleme için farklı tema
filter:client.menuMenü ağacı kurulduktan sonraPanelin yönetmediği düğümü eklemek, çıkarmak, sıralamak
filter:client.breadcrumbİz kısaltılmadan önceİlk kırıntıyı yeniden adlandırmak ya da köklemek
filter:client.predefined_dataMüşteri veri paketinin sonuRota bilinirken bir paket değerini düzeltmek
filter:client.routesRotalar eşleştirilmeden önceBu temanın yayımladığı sayfaya kısa adres
filter:routing.matchSon yönlendirme yedeğiGerçek bir rotanın sahiplenmediği slug'ı cevaplamak
gate:client.page_accessController kurulmadan önceSayfayı kanonik hostuna göndermek ya da reddetmek
filter:sitemap.linksSite haritası toplanırkenSayfanın kanonik ilan ettiği adresi yayımlamak

Örnek

Eksiksiz bir kanca dosyası: tek değişken dinleyicisi, bir çıktı filtresi, bir enjeksiyon.

templates/website/Acme/hooks.php
/*
 * Tüm tema için TEK değişken dinleyicisi: view katmanı yalnız SON dönen diziyi
 * saklar, ikinci bir kayıt bunun yazdığı her şeyi düşürürdü.
 */
Hook::add("filter:template.variables", 1, function ($template, $data) {

    // Şablonun kendi başına okuyamayacağı çekirdek ayarından türetilir.
    $data["support_enabled"] = (int) (Config::get("options/ticket-system") ?? 0) === 1;

    // Bu temanın 404 döndürdüğü rotalar, anahtarlı harita olarak: sandbox'ta in_array()
    // yoktur, bu yüzden şablon {if !$route_off.affiliate} diye sorar.
    $data["route_off"] = array_fill_keys(Theme::active()->meta()["disabled_routes"] ?? [], true);

    // HER sayfada çalışır, bu yüzden sorgusu olan her şey önbellekten geçer. Anahtar
    // para birimini VE dili taşır, çünkü çıktı ikisine birden bağlıdır; biri eksik
    // kalırsa bir ziyaretçiye diğerinin kopyası servis edilir.
    $data["footer_groups"] = Cache::remember('website',
        'acme_footer_' . Money::getUCID() . '_' . Language::selected(), 3600,
        fn (): array => Products::groups());

    // Sayfaya özel iş ikinci bir kayıt değil, bir yol sınamasının arkasında durur.
    if (str_ends_with(str_replace('\\', '/', (string) $template), '/page/pricing.php'))
        $data["plans"] = AcmePricing::cards();

    return $data;   // başka bir şey döndürmek platformun topladığı her anahtarı düşürür
});

/*
 * Bitmiş sayfa. Bağlantı üreten her yükü yeniden yazmaktan hem ucuz hem güvenli:
 * tek yer, ve bir tanesini bile atlayamaz.
 */
Hook::add('filter:client.page.output', 1, function (&$output, $view, $engine) {
    if (str_contains($view, '/')) return;   // parçalar belge değildir

    $output = str_replace('</body>', '<script src="/assets/acme-widget.js" defer></script></body>', $output);
});

/*
 * Veri değil markup: yerleşimin noktaları bir dize alır ve olduğu gibi basar. Burada
 * kaydedilir, böylece temanın ek stil dosyası için hiçbir yerleşim dosyası düzenlenmez.
 */
Hook::add("ui:client.head.css", 1, fn (): string =>
    '<link rel="stylesheet" href="' . Theme::active()->assetUrl('css/extra.css') . '">');
noktaları yayımlayan yerleşim
<head>
    {block name=head}{/block}
    {hook name='ui:client.head.css'}
    {hook name='ui:client.head.js'}
</head>
<body>
    {hook name='ui:client.body.begin'}

    {* Kanca dosyasının koyduğu bir değer, başka her değişken gibi okunur. *}
    {if !$route_off.affiliate}<a href="{link route='affiliate'}">{lang key='nav_affiliate'}</a>{/if}

    {block name=content}{/block}
    {hook name='ui:client.body.end'}
</body>

Tuzaklar

İkinci değişken dinleyicisi birincininkini siler

Hata, ona sebep olan dosyada görünmez: kırılan sayfa, diğer dinleyicinin set ettiği değişkeni okur. Tek gövde tutun, anahtarları oraya ekleyin.

Referanslı çalıştırmada her argüman referanslıdır

Literal, satır içi dizi, tip dönüşümü, null birleştirmeli okuma ya da fonksiyon dönüşü geçemez; PHP uyarı değil ölümcül hata verir. Bağlamı önce bir değişkene alın.

Dinleyici sayfa başına bir maliyettir

Dinleyicideki çıplak bir sorgu her sayfa görüntülemesinde çalışır. Veritabanı okumalarını önbelleğe alın; sonuç dile ve para birimine bağlıysa anahtarı ikisiyle kurun.

Dinleyicinin içindeki ölümcül hata yutulur

Dağıtıcı onu yakalar ve devam eder; ölen dinleyici hata bırakmaz, değiştirmesi gereken değer değişmeden gelir. Bir dinleyici hiçbir şey yapmıyorsa kaydından değil içindeki ölümcül hatadan şüphelenin.

Enjekte edilen markup sayfaya ham ulaşır

Markup noktaları escape etmez; sorumluluk enjekte eden taraftadır. Noktanın döndürdüğü dizeye ziyaretçi girdisi eklemeyin.

Faydalı oldu mu?

Geri bildiriminiz için teşekkürler!

Hâlâ Yardıma mı İhtiyacınız Var?

Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.