Kendi etiketlerinizi oluşturma
Bu sayfa, Latte'de özel etiketler oluşturmak için kapsamlı bir kılavuz sağlar. Latte'nin şablonları nasıl derlediğini anlamanız üzerine inşa ederek, basit etiketlerden iç içe geçmiş içerik ve özel ayrıştırma ihtiyaçları olan daha karmaşık senaryolara kadar her şeyi ele alacağız.
Özel etiketler, şablon sözdizimi ve oluşturma mantığı üzerinde en yüksek düzeyde kontrol sağlar, ancak aynı zamanda en karmaşık genişletme noktasıdır. Özel bir etiket oluşturmaya karar vermeden önce, her zaman daha basit bir çözüm olup olmadığını veya standart sette uygun bir etiketin zaten mevcut olup olmadığını düşünün. Özel etiketleri yalnızca daha basit alternatifler ihtiyaçlarınız için yeterli olmadığında kullanın.
Derleme sürecini anlama
Özel etiketleri etkili bir şekilde oluşturmak için Latte'nin şablonları nasıl işlediğini açıklamak faydalıdır. Bu süreci anlamak, etiketlerin neden bu şekilde yapılandırıldığını ve daha geniş bağlama nasıl uyduklarını açıklığa kavuşturur.
Latte'de şablon derlemesi, basitleştirilmiş olarak şu temel adımları içerir:
- Sözcüksel analiz: Lexer, şablon kaynak kodunu (
.latte
dosyası) okur ve onu token adı verilen küçük, farklı parçalara ayırır (ör.{
,foreach
,$variable
,}
, HTML metni, vb.). - Ayrıştırma: Parser, bu token akışını alır ve şablonun mantığını ve içeriğini temsil eden anlamlı bir ağaç yapısı oluşturur. Bu ağaca soyut sözdizimi ağacı (AST) denir.
- Derleme geçişleri: PHP kodu oluşturmadan önce Latte, derleme geçişlerini çalıştırır. Bunlar, tüm AST'yi dolaşan ve onu değiştirebilen veya bilgi toplayabilen fonksiyonlardır. Bu adım, güvenlik (Sandbox) veya optimizasyon gibi özellikler için çok önemlidir.
- Kod oluşturma: Son olarak, derleyici (potansiyel olarak değiştirilmiş) AST'yi dolaşır ve karşılık gelen PHP sınıf kodunu oluşturur. Bu PHP kodu, çalıştırıldığında şablonu gerçekten oluşturan şeydir.
- Önbellekleme: Oluşturulan PHP kodu diske kaydedilir, bu da sonraki oluşturmaları çok hızlı hale getirir, çünkü 1–4 adımları atlanır.
Aslında, derleme biraz daha karmaşıktır. Latte'nin iki lexer'ı ve parser'ı vardır: biri HTML şablonu için, diğeri etiketler içindeki PHP benzeri kod için. Ayrıca ayrıştırma, tokenizasyondan sonra gerçekleşmez, ancak lexer ve parser paralel olarak iki “iş parçacığında” çalışır ve koordine olur. İnanın bana, bunu programlamak roket bilimiydi :-)
Şablon içeriğinin yüklenmesinden, ayrıştırılmasına ve sonuç dosyasının oluşturulmasına kadar tüm süreci, deneyebileceğiniz ve ara sonuçları yazdırabileceğiniz bu kodla sıralayabilirsiniz:
$latte = new Latte\Engine;
$source = $latte->getLoader()->getContent($file);
$ast = $latte->parse($source);
$latte->applyPasses($ast);
$code = $latte->generate($ast, $file);
Bir etiketin anatomisi
Latte'de tam işlevsel bir özel etiket oluşturmak, birkaç birbirine bağlı parçayı içerir. Uygulamaya geçmeden önce, HTML ve Belge Nesne Modeli (DOM) ile bir analoji kullanarak temel kavramları ve terminolojiyi anlayalım.
Etiketler ve Düğümler (HTML ile Analoji)
HTML'de <p>
veya <div>...</div>
gibi etiketler yazarız. Bu etiketler kaynak
kodundaki sözdizimidir. Tarayıcı bu HTML'yi ayrıştırdığında, Belge Nesne Modeli (DOM) adı verilen bir bellek
temsili oluşturur. DOM'da, HTML etiketleri düğümlerle temsil edilir (özellikle JavaScript DOM terminolojisinde
Element
düğümleri). Programatik olarak bu düğümlerle çalışırız (örneğin, JavaScript
document.getElementById(...)
bir Element düğümü döndürür). Etiket, kaynak dosyadaki yalnızca metinsel bir
temsildir; düğüm, mantıksal ağaçtaki nesne temsilidir.
Latte benzer şekilde çalışır:
.latte
şablon dosyasında{foreach ...}
ve{/foreach}
gibi Latte etiketleri yazarsınız. Bu, sizin şablon yazarı olarak çalıştığınız sözdizimidir.- Latte şablonu ayrıştırdığında, bir Soyut Sözdizimi Ağacı (AST) oluşturur. Bu ağaç düğümlerden oluşur. Şablondaki her Latte etiketi, HTML öğesi, metin parçası veya ifade, bu ağaçta bir veya daha fazla düğüm haline gelir.
- AST'deki tüm düğümler için temel sınıf
Latte\Compiler\Node
'dur. Tıpkı DOM'un farklı düğüm türlerine (Element, Text, Comment) sahip olması gibi, Latte AST'sinin de farklı düğüm türleri vardır. Statik metin içinLatte\Compiler\Nodes\TextNode
, HTML öğeleri içinLatte\Compiler\Nodes\Html\ElementNode
, etiketler içindeki ifadeler içinLatte\Compiler\Nodes\Php\ExpressionNode
ve özel etiketler için kritik olarakLatte\Compiler\Nodes\StatementNode
'dan miras alan düğümlerle karşılaşacaksınız.
Neden StatementNode
?
HTML öğeleri (Html\ElementNode
) öncelikle yapıyı ve içeriği temsil eder. PHP ifadeleri
(Php\ExpressionNode
) değerleri veya hesaplamaları temsil eder. Peki ya {if}
, {foreach}
veya özel {datetime}
etiketimiz gibi Latte etiketleri? Bu etiketler eylemler gerçekleştirir, program
akışını kontrol eder veya mantığa dayalı çıktı üretir. Bunlar, Latte'yi sadece bir işaretleme dili değil, güçlü
bir şablonlama motoru yapan işlevsel birimlerdir.
Programlamada, eylemleri gerçekleştiren bu tür birimlere genellikle “statements” (deyimler) denir. Bu nedenle, bu
işlevsel Latte etiketlerini temsil eden düğümler tipik olarak Latte\Compiler\Nodes\StatementNode
'dan miras alır.
Bu, onları tamamen yapısal düğümlerden (HTML öğeleri gibi) veya değerleri temsil eden düğümlerden (ifadeler gibi)
ayırır.
Anahtar bileşenler
Özel bir etiket oluşturmak için gereken ana bileşenleri gözden geçirelim:
Etiket ayrıştırma fonksiyonu
- Bu PHP çağrılabilir fonksiyonu, kaynak şablondaki Latte etiketi sözdizimini (
{...}
) ayrıştırır. - Etiket hakkındaki bilgileri (adı, konumu ve n:nitelik olup olmadığı gibi) Latte\Compiler\Tag nesnesi aracılığıyla alır.
- Etiket ayırıcıları içindeki argümanları ve ifadeleri ayrıştırmak için birincil aracı,
$tag->parser
aracılığıyla erişilebilen Latte\Compiler\TagParser nesnesidir (bu, tüm şablonu ayrıştıran parser'dan farklıdır). - Eşli etiketler için, başlangıç ve bitiş etiketleri arasındaki iç içeriği ayrıştırması için Latte'ye sinyal
vermek üzere
yield
kullanır. - Ayrıştırma fonksiyonunun nihai hedefi, AST'ye eklenen bir düğüm sınıfı örneği oluşturmak ve döndürmektir.
- Ayrıştırma fonksiyonunu doğrudan ilgili düğüm sınıfında statik bir metot (genellikle
create
olarak adlandırılır) olarak uygulamak gelenekseldir (zorunlu olmasa da). Bu, ayrıştırma mantığını ve düğüm temsilini tek bir pakette düzgün bir şekilde tutar, gerekirse sınıfın özel/korumalı üyelerine erişime izin verir ve organizasyonu iyileştirir.
Düğüm sınıfı
- Etiketinizin Soyut Sözdizimi Ağacı (AST) içindeki mantıksal işlevini temsil eder.
- Ayrıştırılmış bilgileri (argümanlar veya içerik gibi) genel özellikler olarak içerir. Bu özellikler genellikle
diğer
Node
örneklerini içerir (ör. ayrıştırılmış argümanlar içinExpressionNode
, ayrıştırılmış içerik içinAreaNode
). print(PrintContext $context): string
metodu, şablon oluşturma sırasında etiketin eylemini gerçekleştiren PHP kodunu (bir deyim veya bir dizi deyim) oluşturur.getIterator(): \Generator
metodu, derleme geçişleri tarafından gezinme için alt düğümleri (argümanlar, içerik) erişilebilir kılar. Geçişlerin potansiyel olarak alt düğümleri değiştirmesine veya değiştirmesine izin vermek için referanslar (&
) sağlamalıdır.- Tüm şablon AST'ye ayrıştırıldıktan sonra, Latte bir dizi derleme
geçişi çalıştırır. Bu geçişler, her düğüm tarafından sağlanan
getIterator()
metodunu kullanarak tüm AST'yi dolaşır. Düğümleri inceleyebilir, bilgi toplayabilir ve hatta ağacı değiştirebilirler (ör. düğümlerin genel özelliklerini değiştirerek veya düğümleri tamamen değiştirerek). Kapsamlı birgetIterator()
gerektiren bu tasarım çok önemlidir. Sandbox gibi güçlü özelliklerin, özel etiketleriniz de dahil olmak üzere şablonun herhangi bir bölümünün davranışını analiz etmesine ve potansiyel olarak değiştirmesine olanak tanıyarak güvenlik ve tutarlılık sağlar.
Bir uzantı aracılığıyla kayıt
- Latte'ye yeni etiketiniz hakkında ve bunun için hangi ayrıştırma fonksiyonunun kullanılacağını bildirmeniz gerekir. Bu, bir Latte uzantısı içinde yapılır.
- Uzantı sınıfınızın içinde
getTags(): array
metodunu uygularsınız. Bu metot, anahtarların etiket adları (ör.'mytag'
,'n:myattribute'
) ve değerlerin ilgili ayrıştırma fonksiyonlarını temsil eden PHP çağrılabilir fonksiyonları (ör.MyNamespace\DatetimeNode::create(...)
) olduğu ilişkisel bir dizi döndürür.
Özet: Etiket ayrıştırma fonksiyonu, etiketinizin şablon kaynak kodunu bir AST düğümüne
dönüştürür. Düğüm sınıfı daha sonra kendisini derlenmiş şablon için yürütülebilir PHP
koduna dönüştürebilir ve alt düğümlerini getIterator()
aracılığıyla derleme geçişleri için
erişilebilir kılar. Uzantı aracılığıyla kayıt, etiket adını ayrıştırma fonksiyonuyla ilişkilendirir ve
Latte'ye bildirir.
Şimdi bu bileşenleri adım adım nasıl uygulayacağımızı inceleyelim.
Basit bir etiket oluşturma
İlk özel Latte etiketinizi oluşturmaya başlayalım. Çok basit bir örnekle başlayacağız: mevcut tarih ve saati
yazdıran {datetime}
adlı bir etiket. Başlangıçta bu etiket hiçbir argüman kabul etmeyecek, ancak daha
sonra “Etiket Argümanlarını Ayrıştırma” bölümünde geliştireceğiz. Ayrıca
iç içeriği de yoktur.
Bu örnek size temel adımları gösterecektir: düğüm sınıfını tanımlama, print()
ve
getIterator()
metotlarını uygulama, ayrıştırma fonksiyonunu oluşturma ve son olarak etiketi kaydetme.
Hedef: PHP date()
fonksiyonunu kullanarak mevcut tarih ve saati çıktılamak için
{datetime}
'i uygulamak.
Düğüm sınıfının oluşturulması
Öncelikle, Soyut Sözdizimi Ağacı'nda (AST) etiketimizi temsil edecek bir sınıfa ihtiyacımız var. Yukarıda
tartışıldığı gibi, Latte\Compiler\Nodes\StatementNode
'dan miras alıyoruz.
Bir dosya oluşturun (ör. DatetimeNode.php
) ve sınıfı tanımlayın:
<?php
namespace App\Latte;
use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\PrintContext;
use Latte\Compiler\Tag;
class DatetimeNode extends StatementNode
{
/**
* {datetime} bulunduğunda çağrılan etiket ayrıştırma fonksiyonu.
*/
public static function create(Tag $tag): self
{
// Basit etiketimiz şu anda hiçbir argüman kabul etmiyor, bu yüzden hiçbir şeyi ayrıştırmamız gerekmiyor
$node = $tag->node = new self;
return $node;
}
/**
* Şablon oluşturulurken çalıştırılacak PHP kodunu oluşturur.
*/
public function print(PrintContext $context): string
{
return $context->format(
'echo date(\'Y-m-d H:i:s\') %line;',
$this->position,
);
}
/**
* Latte derleme geçişleri için alt düğümlere erişim sağlar.
*/
public function &getIterator(): \Generator
{
false && yield;
}
}
Latte bir şablonda {datetime}
ile karşılaştığında, create()
ayrıştırma fonksiyonunu
çağırır. Görevi, bir DatetimeNode
örneği döndürmektir.
print()
metodu, şablon oluşturulurken çalıştırılacak PHP kodunu oluşturur. Derlenmiş şablon için sonuç
PHP kod dizesini oluşturan $context->format()
metodunu çağırıyoruz. İlk argüman,
'echo date('Y-m-d H:i:s') %line;'
, sonraki parametrelerin eklendiği bir maskedir. %line
yer tutucusu,
format()
metoduna ikinci argümanı, yani $this->position
'ı kullanmasını ve oluşturulan PHP
kodunu orijinal şablon satırına geri bağlayan /* line 15 */
gibi bir yorum eklemesini söyler, bu da hata
ayıklama için çok önemlidir.
$this->position
özelliği, temel Node
sınıfından miras alınır ve Latte parser tarafından
otomatik olarak ayarlanır. Etiketin kaynak .latte
dosyasında nerede bulunduğunu gösteren bir Latte\Compiler\Position nesnesi içerir.
getIterator()
metodu, derleme geçişleri için çok önemlidir. Tüm alt düğümleri sağlamalıdır, ancak
basit DatetimeNode
'umuzun şu anda hiçbir argümanı veya içeriği yoktur, dolayısıyla alt düğümü yoktur.
Ancak, metot yine de mevcut olmalı ve bir üreteç olmalıdır, yani yield
anahtar kelimesi metot gövdesinde bir
şekilde bulunmalıdır.
Bir uzantı aracılığıyla kayıt
Son olarak, Latte'ye yeni etiket hakkında bilgi verelim. Bir uzantı sınıfı oluşturun (ör.
MyLatteExtension.php
) ve etiketi getTags()
metodunda kaydedin.
<?php
namespace App\Latte;
use Latte\Extension;
class MyLatteExtension extends Extension
{
/**
* Bu uzantı tarafından sağlanan etiketlerin listesini döndürür.
* @return array<string, callable> Harita: 'etiket-adı' => ayrıştırma-fonksiyonu
*/
public function getTags(): array
{
return [
'datetime' => DatetimeNode::create(...),
// Daha sonra buraya daha fazla etiket kaydedin
];
}
}
Ardından bu uzantıyı Latte Motoru'nda kaydedin:
$latte = new Latte\Engine;
$latte->addExtension(new App\Latte\MyLatteExtension);
Bir şablon oluşturun:
<p>Sayfa oluşturuldu: {datetime}</p>
Beklenen çıktı: <p>Sayfa oluşturuldu: 2023-10-27 11:00:00</p>
Bu aşamanın özeti
Temel bir özel {datetime}
etiketi başarıyla oluşturduk. AST'deki temsilini (DatetimeNode
)
tanımladık, ayrıştırmasını (create()
) ele aldık, nasıl PHP kodu oluşturması gerektiğini
(print()
) belirttik, alt öğelerinin geçiş için erişilebilir olmasını (getIterator()
) sağladık
ve Latte'de kaydettik.
Bir sonraki bölümde, bu etiketi argümanları kabul edecek şekilde geliştireceğiz ve ifadeleri nasıl ayrıştıracağımızı ve alt düğümleri nasıl yöneteceğimizi göstereceğiz.
Etiket argümanlarını ayrıştırma
Basit {datetime}
etiketimiz çalışıyor, ancak çok esnek değil. date()
fonksiyonu için isteğe
bağlı bir argüman kabul edecek şekilde geliştirelim: bir biçimlendirme dizesi. Gerekli sözdizimi
{datetime $format}
olacaktır.
Hedef: {datetime}
'i, date()
için biçimlendirme dizesi olarak kullanılacak isteğe bağlı
bir PHP ifadesini argüman olarak kabul edecek şekilde değiştirmek.
TagParser
ile tanışma
Kodu değiştirmeden önce, kullanacağımız aracı anlamak önemlidir: Latte\Compiler\TagParser. Ana Latte parser'ı
(TemplateParser
), {datetime ...}
veya bir n:nitelik gibi bir Latte etiketiyle karşılaştığında,
etiketin içindeki içeriğin ayrıştırılmasını ( {
ve }
arasındaki kısım veya nitelik
değeri) özel bir TagParser
'a devreder.
Bu TagParser
yalnızca etiket argümanları ile çalışır. Görevi, bu argümanları temsil eden
tokenları işlemektir. Anahtar nokta, kendisine sağlanan tüm içeriği işlemesi gerektiğidir. Ayrıştırma
fonksiyonunuz biterse ancak TagParser
argümanların sonuna ulaşmadıysa ($tag->parser->isEnd()
aracılığıyla kontrol edilir), Latte bir istisna fırlatır, çünkü bu, etiket içinde beklenmeyen tokenların kaldığını
gösterir. Tersine, etiket argümanları gerektiriyorsa, ayrıştırma fonksiyonunuzun başında
$tag->expectArguments()
'i çağırmalısınız. Bu metot, argümanların mevcut olup olmadığını kontrol eder
ve etiket herhangi bir argüman olmadan kullanıldıysa yardımcı bir istisna fırlatır.
TagParser
, çeşitli türde argümanları ayrıştırmak için kullanışlı metotlar sunar:
parseExpression(): ExpressionNode
: PHP benzeri bir ifadeyi (değişkenler, değişmezler, operatörler, fonksiyon/metot çağrıları vb.) ayrıştırır. Latte'nin sözdizimsel şekerlemesini, örneğin basit alfanümerik dizeleri tırnak içine alınmış dizeler gibi ele almasını (ör.foo
,'foo'
gibi ayrıştırılır) yönetir.parseUnquotedStringOrExpression(): ExpressionNode
: Standart bir ifadeyi veya tırnaksız bir dizeyi ayrıştırır. Tırnaksız dizeler, Latte tarafından tırnak işaretleri olmadan izin verilen dizilerdir, genellikle dosya yolları gibi şeyler için kullanılır (ör.{include ../file.latte}
). Tırnaksız bir dize ayrıştırırsa, birStringNode
döndürür.parseArguments(): ArrayNode
:10, name: 'John', true
gibi potansiyel olarak anahtarlarla virgülle ayrılmış argümanları ayrıştırır.parseModifier(): ModifierNode
:|upper|truncate:10
gibi filtreleri ayrıştırır.parseType(): ?SuperiorTypeNode
:int
,?string
,array|Foo
gibi PHP tür ipuçlarını ayrıştırır.
Daha karmaşık veya daha düşük seviyeli ayrıştırma ihtiyaçları için, token akışı ile
$tag->parser->stream
aracılığıyla doğrudan etkileşim kurabilirsiniz. Bu nesne, tek tek tokenları kontrol
etmek ve işlemek için metotlar sağlar:
$tag->parser->stream->is(...): bool
: Mevcut tokenın belirtilen türlerden (ör.Token::Php_Variable
) veya değişmez değerlerden (ör.'as'
) herhangi biriyle eşleşip eşleşmediğini tüketmeden kontrol eder. İleriye bakmak için kullanışlıdır.$tag->parser->stream->consume(...): Token
: Mevcut tokenı tüketir ve akış konumunu ileri taşır. Argüman olarak beklenen token türleri/değerleri sağlanırsa ve mevcut token eşleşmezse, birCompileException
fırlatır. Belirli bir tokenı beklediğinizde bunu kullanın.$tag->parser->stream->tryConsume(...): ?Token
: Mevcut tokenı yalnızca belirtilen türlerden/değerlerden biriyle eşleşiyorsa tüketmeye çalışır. Eşleşirse, tokenı tüketir ve döndürür. Eşleşmezse, akış konumunu değiştirmeden bırakır venull
döndürür. İsteğe bağlı tokenlar için veya farklı sözdizimsel yollar arasında seçim yaparken bunu kullanın.
create()
ayrıştırma fonksiyonunu güncelleme
Bu anlayışla, DatetimeNode
'daki create()
metodunu, $tag->parser
kullanarak isteğe
bağlı biçimlendirme argümanını ayrıştıracak şekilde değiştirelim.
<?php
namespace App\Latte;
use Latte\Compiler\Nodes\Php\ExpressionNode;
use Latte\Compiler\Nodes\Php\Scalar\StringNode;
use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\PrintContext;
use Latte\Compiler\Tag;
class DatetimeNode extends StatementNode
{
// Ayrıştırılmış biçim ifadesi düğümünü tutmak için genel bir özellik ekleyin
public ?ExpressionNode $format = null;
public static function create(Tag $tag): self
{
$node = $tag->node = new self;
// Herhangi bir token olup olmadığını kontrol edin
if (!$tag->parser->isEnd()) {
// Argümanı TagParser kullanarak PHP benzeri bir ifade olarak ayrıştırın.
$node->format = $tag->parser->parseExpression();
}
return $node;
}
// ... print() ve getIterator() metotları daha sonra güncellenecektir ...
}
Genel bir $format
özelliği ekledik. create()
içinde şimdi argümanların var olup
olmadığını kontrol etmek için $tag->parser->isEnd()
kullanıyoruz. Varsa,
$tag->parser->parseExpression()
ifade için tokenları işler. TagParser
tüm giriş tokenlarını
işlemesi gerektiğinden, kullanıcı biçim ifadesinden sonra beklenmeyen bir şey yazarsa (ör.
{datetime 'Y-m-d', unexpected}
) Latte otomatik olarak bir hata fırlatır.
print()
metodunu güncelleme
Şimdi print()
metodunu, $this->format
içinde saklanan ayrıştırılmış biçim ifadesini
kullanacak şekilde değiştirelim. Hiçbir biçim sağlanmadıysa ($this->format
null
ise),
varsayılan bir biçimlendirme dizesi kullanmalıyız, örneğin 'Y-m-d H:i:s'
.
public function print(PrintContext $context): string
{
$formatNode = $this->format ?? new StringNode('Y-m-d H:i:s');
// %node, $formatNode'un PHP kod temsilini yazdırır.
return $context->format(
'echo date(%node) %line;',
$formatNode,
$this->position
);
}
$formatNode
değişkeninde, PHP date()
fonksiyonu için biçimlendirme dizesini temsil eden AST
düğümünü saklıyoruz. Burada null birleştirme operatörünü (??
) kullanıyoruz. Kullanıcı şablonda bir
argüman sağladıysa (ör. {datetime 'd.m.Y'}
), o zaman $this->format
özelliği karşılık gelen
düğümü içerir (bu durumda değeri 'd.m.Y'
olan bir StringNode
) ve bu düğüm kullanılır.
Kullanıcı bir argüman sağlamadıysa (sadece {datetime}
yazdıysa), $this->format
özelliği
null
olur ve bunun yerine varsayılan 'Y-m-d H:i:s'
biçimiyle yeni bir StringNode
oluştururuz. Bu, $formatNode
'un her zaman biçim için geçerli bir AST düğümü içermesini sağlar.
'echo date(%node) %line;'
maskesinde, format()
metoduna bir sonraki argümanı (bizim
$formatNode
'umuz) almasını, print()
metodunu çağırmasını (PHP kod temsilini döndürecektir) ve
sonucu yer tutucunun konumuna eklemesini söyleyen yeni bir %node
yer tutucusu kullanılır.
Alt düğümler için getIterator()
uygulama
DatetimeNode
'umuzun şimdi bir alt düğümü var: $format
ifadesi. Bu alt düğümü,
getIterator()
metodunda sağlayarak derleme geçişlerine erişilebilir kılmalıyız. Geçişlerin potansiyel olarak
düğümü değiştirmesine izin vermek için bir referans (&
) sağlamayı unutmayın.
public function &getIterator(): \Generator
{
if ($this->format) {
yield $this->format;
}
}
Bu neden çok önemli? $format
argümanının yasaklanmış bir fonksiyon çağrısı içerip içermediğini
kontrol etmesi gereken bir Sandbox geçişi düşünün (ör. {datetime dangerousFunction()}
).
getIterator()
$this->format
'ı sağlamazsa, Sandbox geçişi etiketimizin argümanı içindeki
dangerousFunction()
çağrısını asla görmezdi, bu da potansiyel bir güvenlik açığı yaratırdı. Sağlayarak,
Sandbox'ın (ve diğer geçişlerin) $format
ifade düğümünü kontrol etmesine ve potansiyel olarak
değiştirmesine izin veririz.
Geliştirilmiş etiketi kullanma
Etiket şimdi isteğe bağlı argümanı doğru şekilde işliyor:
Varsayılan biçim: {datetime}
Özel biçim: {datetime 'd.m.Y'}
Değişken kullanımı: {datetime $userDateFormatPreference}
{* Bu, 'd.m.Y' ayrıştırıldıktan sonra bir hataya neden olur, çünkü ", foo" beklenmiyor *}
{* {datetime 'd.m.Y', foo} *}
Ardından, aralarındaki içeriği işleyen eşli etiketler oluşturmaya bakacağız.
Eşli etiketleri işleme
Şimdiye kadar, {datetime}
etiketimiz kendiliğinden kapanan (kavramsal olarak) idi. Başlangıç ve
bitiş etiketleri arasında hiçbir içeriği yoktu. Ancak, birçok kullanışlı etiket bir şablon içeriği bloğuyla
çalışır. Bunlara eşli etiketler denir. Örnekler arasında {if}...{/if}
, {block}...{/block}
veya şimdi oluşturacağımız özel bir etiket bulunur: {debug}...{/debug}
.
Bu etiket, şablonlarımıza yalnızca geliştirme sırasında görünür olması gereken hata ayıklama bilgilerini eklememize olanak tanır.
Hedef: İçeriği yalnızca belirli bir “geliştirme modu” bayrağı etkin olduğunda oluşturulan eşli bir
{debug}
etiketi oluşturmak.
Sağlayıcılarla tanışma
Bazen etiketlerinizin, doğrudan şablon parametreleri olarak iletilmeyen verilere veya hizmetlere erişmesi gerekir. Örneğin, uygulamanın geliştirme modunda olup olmadığını belirlemek, kullanıcı nesnesine erişmek veya yapılandırma değerlerini almak. Latte bu amaçla sağlayıcılar (Providers) adı verilen bir mekanizma sağlar.
Sağlayıcılar, uzantınızda
getProviders()
metodu kullanılarak kaydedilir. Bu metot, anahtarların sağlayıcıların şablon çalışma zamanı
kodunda erişilebilir olacağı adlar olduğu ve değerlerin gerçek veriler veya nesneler olduğu ilişkisel bir dizi
döndürür.
Etiketinizin print()
metodu tarafından oluşturulan PHP kodu içinde, bu sağlayıcılara özel
$this->global
nesne özelliği aracılığıyla erişebilirsiniz. Bu özellik tüm uzantılar arasında
paylaşıldığından, Latte'nin temel sağlayıcıları veya diğer üçüncü taraf uzantılarından gelen sağlayıcılarla
olası ad çakışmalarını önlemek için sağlayıcı adlarınıza önek eklemek iyi bir uygulamadır. Yaygın bir
kural, üreticinizle veya uzantı adınızla ilgili kısa, benzersiz bir önek kullanmaktır. Örneğimiz için app
önekini kullanacağız ve geliştirme modu bayrağı $this->global->appDevMode
olarak erişilebilir
olacaktır.
İçerik ayrıştırma için yield
anahtar kelimesi
Latte parser'ına {debug}
ve {/debug}
arasındaki içeriği işlemesini nasıl söyleriz?
İşte burada yield
anahtar kelimesi devreye girer.
yield
bir create()
fonksiyonunda kullanıldığında, fonksiyon bir PHP üreteci haline gelir. Yürütmesi duraklatılır ve
kontrol ana TemplateParser
'a geri döner. TemplateParser
daha sonra şablon içeriğini karşılık
gelen kapatma etiketine ({/debug}
bizim durumumuzda) rastlayana kadar ayrıştırmaya devam eder.
Kapatma etiketi bulunduğunda, TemplateParser
create()
fonksiyonumuzun yürütmesini
yield
deyiminden hemen sonra devam ettirir. yield
deyimi tarafından döndürülen değer, iki
öğe içeren bir dizidir:
- Başlangıç ve bitiş etiketleri arasındaki ayrıştırılmış içeriği temsil eden bir
AreaNode
. - Kapatma etiketini temsil eden bir
Tag
nesnesi (ör.{/debug}
).
yield
kullanan bir DebugNode
sınıfı ve create
metodunu oluşturalım.
<?php
namespace App\Latte;
use Latte\Compiler\Nodes\AreaNode;
use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\PrintContext;
use Latte\Compiler\Tag;
class DebugNode extends StatementNode
{
// Ayrıştırılmış iç içeriği tutmak için genel özellik
public AreaNode $content;
/**
* Eşli etiket {debug} ... {/debug} için ayrıştırma fonksiyonu.
*/
public static function create(Tag $tag): \Generator // dönüş türüne dikkat edin
{
$node = $tag->node = new self;
// Ayrıştırmayı duraklatın, iç içeriği ve {/debug} bulunduğunda bitiş etiketini alın
[$node->content, $endTag] = yield;
return $node;
}
// ... print() ve getIterator() daha sonra uygulanacaktır ...
}
Not: Etiket bir n:nitelik olarak kullanılıyorsa, yani <div n:debug>...</div>
ise
$endTag
null
olur.
Koşullu oluşturma için print()
uygulama
print()
metodu şimdi çalışma zamanında appDevMode
sağlayıcısını kontrol eden ve bayrak true
ise yalnızca iç içerik için kodu yürüten PHP kodu oluşturmalıdır.
public function print(PrintContext $context): string
{
// Çalışma zamanında sağlayıcıyı kontrol eden bir PHP 'if' deyimi oluşturur
return $context->format(
<<<'XX'
if ($this->global->appDevMode) %line {
// Geliştirme modundaysa, iç içeriği yazdırın
%node
}
XX,
$this->position, // %line yorumu için
$this->content, // İç içeriğin AST'sini içeren düğüm
);
}
Bu basittir. Standart bir PHP if
deyimi oluşturmak için PrintContext::format()
kullanıyoruz.
if
içine $this->content
için %node
yer tutucusunu yerleştiriyoruz. Latte, etiketin
iç kısmı için PHP kodunu oluşturmak üzere $this->content->print($context)
'i özyinelemeli olarak
çağırır, ancak yalnızca $this->global->appDevMode
çalışma zamanında true olarak
değerlendirilirse.
İçerik için getIterator()
uygulama
Önceki örnekteki argüman düğümünde olduğu gibi, DebugNode
'umuzun şimdi bir alt düğümü var:
AreaNode $content
. getIterator()
içinde sağlayarak erişilebilir kılmalıyız:
public function &getIterator(): \Generator
{
// İçerik düğümüne bir referans sağlar
yield $this->content;
}
Bu, derleme geçişlerinin {debug}
etiketimizin içeriğine inmesini sağlar, bu da içerik koşullu olarak
oluşturulsa bile önemlidir. Örneğin, Sandbox'ın içeriği appDevMode
'un true veya false olmasına
bakılmaksızın analiz etmesi gerekir.
Kayıt ve kullanım
Etiketi ve sağlayıcıyı uzantınızda kaydedin:
class MyLatteExtension extends Extension
{
// $isDevelopmentMode'un bir yerde belirlendiğini varsayıyoruz (ör. yapılandırmadan)
public function __construct(
private bool $isDevelopmentMode,
) {
}
public function getTags(): array
{
return [
'datetime' => DatetimeNode::create(...),
'debug' => DebugNode::create(...), // Yeni etiketi kaydet
];
}
public function getProviders(): array
{
return [
'appDevMode' => $this->isDevelopmentMode, // Sağlayıcıyı kaydet
];
}
}
// Uzantıyı kaydederken:
$isDev = true; // Bunu uygulamanızın ortamına göre belirleyin
$latte->addExtension(new App\Latte\MyLatteExtension($isDev));
Ve şablonda kullanımı:
<p>Her zaman görünen normal içerik.</p>
{debug}
<div class="debug-panel">
Mevcut kullanıcının ID'si: {$user->id}
İstek zamanı: {=time()}
</div>
{/debug}
<p>Diğer normal içerik.</p>
n:nitelik entegrasyonu
Latte, birçok eşli etiket için kullanışlı bir kısaltılmış gösterim sunar: n:nitelikler. {tag}...{/tag}
gibi eşli bir etiketiniz
varsa ve etkisinin doğrudan tek bir HTML öğesine uygulanmasını istiyorsanız, genellikle bu öğe üzerinde bir
n:tag
niteliği olarak daha kısa bir şekilde yazabilirsiniz.
Tanımladığınız çoğu standart eşli etiket için (bizim {debug}
gibi), Latte karşılık gelen
n:
nitelik sürümünü otomatik olarak etkinleştirir. Kayıt sırasında ek bir şey yapmanız gerekmez:
{* Standart eşli etiket kullanımı *}
{debug}<div>Hata ayıklama bilgisi</div>{/debug}
{* n:nitelik ile eşdeğer kullanım *}
<div n:debug>Hata ayıklama bilgisi</div>
Her iki sürüm de <div>
'i yalnızca $this->global->appDevMode
true ise oluşturur.
inner-
ve tag-
önekleri de beklendiği gibi çalışır.
Bazen etiketinizin mantığının, standart bir eşli etiket olarak mı yoksa bir n:nitelik olarak mı kullanıldığına veya
n:inner-tag
veya n:tag-tag
gibi bir önek kullanılıp kullanılmadığına bağlı olarak biraz farklı
davranması gerekebilir. create()
ayrıştırma fonksiyonunuza iletilen Latte\Compiler\Tag
nesnesi şu
bilgileri sağlar:
$tag->isNAttribute(): bool
: Etiket bir n:nitelik olarak ayrıştırılıyorsatrue
döndürür$tag->prefix: ?string
: n:nitelik ile kullanılan öneki döndürür, bunull
(n:nitelik değil),Tag::PrefixNone
,Tag::PrefixInner
veyaTag::PrefixTag
olabilir
Şimdi basit etiketleri, argüman ayrıştırmayı, eşli etiketleri, sağlayıcıları ve n:nitelikleri anladığımıza
göre, {debug}
etiketimizi başlangıç noktası olarak kullanarak diğer etiketlerin içine yerleştirilmiş
etiketleri içeren daha karmaşık bir senaryoyu ele alalım.
Ara etiketler
Bazı eşli etiketler, son kapatma etiketinden önce içlerinde başka etiketlerin görünmesine izin verir veya hatta
gerektirir. Bunlara ara etiketler denir. Klasik örnekler arasında {if}...{elseif}...{else}...{/if}
veya
{switch}...{case}...{default}...{/switch}
bulunur.
{debug}
etiketimizi, uygulama geliştirme modunda olmadığında oluşturulacak isteğe bağlı bir
{else}
yan tümcesini destekleyecek şekilde genişletelim.
Hedef: {debug}
'i isteğe bağlı bir ara {else}
etiketini destekleyecek şekilde
değiştirmek. Nihai sözdizimi {debug} ... {else} ... {/debug}
olmalıdır.
yield
ile ara etiketleri ayrıştırma
yield
'in create()
ayrıştırma fonksiyonunu duraklattığını ve ayrıştırılmış içeriği
bitiş etiketiyle birlikte döndürdüğünü zaten biliyoruz. Ancak yield
daha fazla kontrol sunar: ona ara
etiket adları dizisi sağlayabilirsiniz. Parser, bu belirtilen etiketlerden herhangi birine aynı iç içe geçme
seviyesinde (yani, üst etiketin doğrudan alt öğeleri olarak, içindeki diğer blokların veya etiketlerin içinde değil)
rastladığında, ayrıştırmayı da durdurur.
Ayrıştırma bir ara etiket nedeniyle durduğunda, içerik ayrıştırmayı durdurur, create()
üretecini devam
ettirir ve kısmen ayrıştırılmış içeriği ve ara etiketin kendisini (son bitiş etiketi yerine) geri iletir.
create()
fonksiyonumuz daha sonra bu ara etiketi işleyebilir (ör. varsa argümanlarını ayrıştırabilir) ve
içeriğin bir sonraki bölümünü son bitiş etiketine veya başka bir beklenen ara etikete kadar ayrıştırmak
için yield
'i tekrar kullanabilir.
{else}
'i beklemek için DebugNode::create()
'i değiştirelim:
<?php
namespace App\Latte;
use Latte\Compiler\Nodes\AreaNode;
use Latte\Compiler\Nodes\NopNode;
use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\PrintContext;
use Latte\Compiler\Tag;
class DebugNode extends StatementNode
{
// {debug} bölümü için içerik
public AreaNode $thenContent;
// {else} bölümü için isteğe bağlı içerik
public ?AreaNode $elseContent = null;
public static function create(Tag $tag): \Generator
{
$node = $tag->node = new self;
// yield ve {/debug} veya {else}'i bekleyin
[$node->thenContent, $nextTag] = yield ['else'];
// Durduğumuz etiketin {else} olup olmadığını kontrol edin
if ($nextTag?->name === 'else') {
// {else} ve {/debug} arasındaki içeriği ayrıştırmak için tekrar yield
[$node->elseContent, $endTag] = yield;
}
return $node;
}
// ... print() ve getIterator() daha sonra güncellenecektir ...
}
Şimdi yield ['else']
Latte'ye sadece {/debug}
için değil, aynı zamanda {else}
için
de ayrıştırmayı durdurmasını söyler. {else}
bulunursa, $nextTag
{else}
için
Tag
nesnesini içerir. Sonra argümansız yield
'i tekrar kullanırız, bu da şimdi yalnızca son
{/debug}
etiketini beklediğimiz anlamına gelir ve sonucu $node->elseContent
'e kaydederiz.
{else}
bulunmazsa, $nextTag
{/debug}
için Tag
olurdu (veya n:nitelik olarak
kullanılıyorsa null
) ve $node->elseContent
null
kalırdı.
{else}
ile print()
uygulama
print()
metodu yeni yapıyı yansıtmalıdır. devMode
sağlayıcısına dayalı bir PHP
if/else
deyimi oluşturmalıdır.
public function print(PrintContext $context): string
{
return $context->format(
<<<'XX'
if ($this->global->appDevMode) %line {
%node // 'then' dalı için kod ({debug} içeriği)
} else {
%node // 'else' dalı için kod ({else} içeriği)
}
XX,
$this->position, // 'if' koşulu için satır numarası
$this->thenContent, // İlk %node yer tutucusu
$this->elseContent ?? new NopNode, // İkinci %node yer tutucusu
);
}
Bu standart bir PHP if/else
yapısıdır. %node
'u iki kez kullanıyoruz; format()
sağlanan düğümleri sırayla değiştirir. $this->elseContent
null
ise hatalardan kaçınmak için
?? new NopNode
kullanıyoruz – NopNode
basitçe hiçbir şey yazdırmaz.
Her iki içerik için getIterator()
uygulama
Şimdi potansiyel olarak iki alt içerik düğümümüz var ($thenContent
ve $elseContent
). Varsa her
ikisini de sağlamalıyız:
public function &getIterator(): \Generator
{
yield $this->thenContent;
if ($this->elseContent) {
yield $this->elseContent;
}
}
Geliştirilmiş etiketi kullanma
Etiket şimdi isteğe bağlı {else}
yan tümcesiyle kullanılabilir:
{debug}
<p>Hata ayıklama bilgileri gösteriliyor, çünkü devMode AÇIK.</p>
{else}
<p>Hata ayıklama bilgileri gizli, çünkü devMode KAPALI.</p>
{/debug}
Durum ve iç içe geçmeyi işleme
Önceki örneklerimiz ({datetime}
, {debug}
) print()
metotları içinde nispeten
durumsuzdu. Ya doğrudan içerik yazdırıyorlardı ya da genel bir sağlayıcıya dayalı basit bir koşullu kontrol
gerçekleştiriyorlardı. Ancak birçok etiket, oluşturma sırasında bir tür durum yönetmeyi gerektirir veya performans
veya doğruluk nedeniyle yalnızca bir kez çalıştırılması gereken kullanıcı ifadelerinin değerlendirilmesini içerir.
Ayrıca, özel etiketlerimiz iç içe geçtiğinde ne olacağını düşünmeliyiz.
Bu kavramları, {repeat $count}...{/repeat}
etiketini oluşturarak gösterelim. Bu etiket, iç içeriğini
$count
kez tekrarlayacaktır.
Hedef: İçeriğini belirtilen sayıda tekrarlayan {repeat $count}
'i uygulamak.
Geçici ve benzersiz değişkenlere duyulan ihtiyaç
Kullanıcının şunu yazdığını hayal edin:
{repeat rand(1, 5)} İçerik {/repeat}
print()
metodumuzda safça bir PHP for
döngüsünü şu şekilde oluşturursak:
// Basitleştirilmiş, YANLIŞ oluşturulan kod
for ($i = 0; $i < rand(1, 5); $i++) {
// içerik çıktısı
}
Bu yanlış olurdu! rand(1, 5)
ifadesi döngünün her iterasyonunda yeniden değerlendirilir, bu da
öngörülemeyen sayıda tekrara yol açar. $count
ifadesini döngü başlamadan bir kez değerlendirmemiz
ve sonucunu saklamamız gerekir.
Önce sayı ifadesini değerlendiren ve onu geçici bir çalışma zamanı değişkeninde saklayan PHP kodu
oluşturacağız. Şablon kullanıcısı tarafından tanımlanan değişkenlerle ve Latte'nin dahili değişkenleriyle (
$ʟ_...
gibi) çakışmaları önlemek için, geçici değişkenlerimiz için $__
(çift alt
çizgi) önek kuralını kullanacağız.
Oluşturulan kod daha sonra şöyle görünür:
$__count = rand(1, 5);
for ($__i = 0; $__i < $__count; $__i++) {
// içerik çıktısı
}
Şimdi iç içe geçmeyi düşünelim:
{repeat $countA} {* Dış döngü *}
{repeat $countB} {* İç döngü *}
...
{/repeat}
{/repeat}
Hem dış hem de iç {repeat}
etiketi aynı geçici değişken adlarını (ör. $__count
ve
$__i
) kullanarak kod oluşturursa, iç döngü dış döngünün değişkenlerinin üzerine yazar ve
mantığı bozar.
Her {repeat}
etiketi örneği için oluşturulan geçici değişkenlerin benzersiz olmasını
sağlamalıyız. Bunu PrintContext::generateId()
kullanarak başarırız. Bu metot, derleme aşamasında benzersiz
bir tamsayı döndürür. Bu ID'yi geçici değişkenlerimizin adlarına ekleyebiliriz.
Yani $__count
yerine, ilk repeat etiketi için $__count_1
, ikincisi için $__count_2
vb.
oluşturacağız. Benzer şekilde, döngü sayacı için $__i_1
, $__i_2
vb. kullanacağız.
RepeatNode
uygulama
Düğüm sınıfını oluşturalım.
<?php
namespace App\Latte;
use Latte\CompileException;
use Latte\Compiler\Nodes\AreaNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;
use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\PrintContext;
use Latte\Compiler\Tag;
class RepeatNode extends StatementNode
{
public ExpressionNode $count;
public AreaNode $content;
/**
* {repeat $count} ... {/repeat} için ayrıştırma fonksiyonu
*/
public static function create(Tag $tag): \Generator
{
$tag->expectArguments(); // $count'un sağlandığından emin olun
$node = $tag->node = new self;
// Sayı ifadesini ayrıştırın
$node->count = $tag->parser->parseExpression();
// İç içeriği alın
[$node->content] = yield;
return $node;
}
/**
* Benzersiz değişken adlarıyla PHP 'for' döngüsü oluşturur.
*/
public function print(PrintContext $context): string
{
// Benzersiz değişken adları oluşturun
$id = $context->generateId();
$countVar = '$__count_' . $id; // ör. $__count_1, $__count_2, vb.
$iteratorVar = '$__i_' . $id; // ör. $__i_1, $__i_2, vb.
return $context->format(
<<<'XX'
// Sayı ifadesini *bir kez* değerlendirin ve saklayın
%raw = (int) (%node);
// Saklanan sayıyı ve benzersiz iterasyon değişkenini kullanarak döngü yapın
for (%raw = 0; %2.raw < %0.raw; %2.raw++) %line {
%node // İç içeriği oluşturun
}
XX,
$countVar, // %0 - Sayıyı saklamak için değişken
$this->count, // %1 - Sayı için ifade düğümü
$iteratorVar, // %2 - Döngü iterasyon değişkeninin adı
$this->position, // %3 - Döngünün kendisi için satır numarası yorumu
$this->content // %4 - İç içerik düğümü
);
}
/**
* Alt düğümleri (sayı ifadesi ve içerik) sağlar.
*/
public function &getIterator(): \Generator
{
yield $this->count;
yield $this->content;
}
}
create()
metodu, parseExpression()
kullanarak gerekli $count
ifadesini ayrıştırır.
Önce $tag->expectArguments()
çağrılır. Bu, kullanıcının {repeat}
'ten sonra bir şey
sağladığından emin olur. $tag->parser->parseExpression()
hiçbir şey sağlanmazsa başarısız olsa da,
hata mesajı beklenmeyen sözdizimi hakkında olabilir. expectArguments()
kullanmak, özellikle {repeat}
etiketi için argümanların eksik olduğunu belirten çok daha net bir hata sağlar.
print()
metodu, çalışma zamanında tekrarlama mantığını yürütmekten sorumlu PHP kodunu oluşturur.
İhtiyaç duyacağı geçici PHP değişkenleri için benzersiz adlar oluşturarak başlar.
$context->format()
metodu, karşılık gelen argüman olarak sağlanan ham dizeyi ekleyen yeni bir
%raw
yer tutucusu ile çağrılır. Burada, $countVar
içinde saklanan benzersiz değişken adını
(ör. $__count_1
) ekler. Peki ya %0.raw
ve %2.raw
? Bu, konumsal yer tutucuları
gösterir. Yalnızca bir sonraki mevcut ham argümanı alan %raw
yerine, %2.raw
açıkça dizin
2'deki argümanı ( $iteratorVar
olan) alır ve ham dize değerini ekler. Bu, $iteratorVar
dizesini
format()
için argüman listesinde birden çok kez iletmeden yeniden kullanmamızı sağlar.
Bu dikkatlice oluşturulmuş format()
çağrısı, sayı ifadesini doğru şekilde işleyen ve
{repeat}
etiketleri iç içe geçtiğinde bile değişken adı çakışmalarını önleyen verimli ve güvenli bir
PHP döngüsü oluşturur.
Kayıt ve kullanım
Etiketi uzantınızda kaydedin:
use App\Latte\RepeatNode;
class MyLatteExtension extends Extension
{
public function getTags(): array
{
return [
'datetime' => DatetimeNode::create(...),
'debug' => DebugNode::create(...),
'repeat' => RepeatNode::create(...), // repeat etiketini kaydet
];
}
}
İç içe geçme dahil şablonda kullanın:
{var $rows = rand(5, 7)}
{var $cols = rand(3, 5)}
{repeat $rows}
<tr>
{repeat $cols}
<td>İç döngü</td>
{/repeat}
</tr>
{/repeat}
Bu örnek, $__
önekli geçici değişkenler ve PrintContext::generateId()
'den alınan benzersiz
ID'ler kullanarak durumun (döngü sayaçları) ve potansiyel iç içe geçme sorunlarının nasıl ele alınacağını
gösterir.
Saf n:nitelikler
n:if
veya n:foreach
gibi birçok n:nitelik
, eşli etiket karşılıkları
({if}...{/if}
, {foreach}...{/foreach}
) için kullanışlı kısaltmalar olarak hizmet ederken, Latte
ayrıca yalnızca n:nitelik biçiminde var olan etiketleri tanımlamanıza da olanak tanır. Bunlar genellikle eklendikleri
HTML öğesinin niteliklerini veya davranışını değiştirmek için kullanılır.
Latte'de yerleşik standart örnekler arasında, class
niteliğini dinamik olarak oluşturmaya yardımcı olan n:class
ve birden çok rastgele nitelik ayarlayabilen n:attr
bulunur.
Kendi saf n:niteliğimizi oluşturalım: n:confirm
, bir eylem gerçekleştirmeden önce (bir bağlantıyı takip
etmek veya bir form göndermek gibi) bir JavaScript onay iletişim kutusu ekler.
Hedef: Kullanıcı onay iletişim kutusunu iptal ederse varsayılan eylemi önlemek için bir onclick
işleyicisi ekleyen n:confirm="'Emin misiniz?'"
uygulamak.
ConfirmNode
uygulama
Bir Node sınıfına ve bir ayrıştırma fonksiyonuna ihtiyacımız var.
<?php
namespace App\Latte;
use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\PrintContext;
use Latte\Compiler\Tag;
use Latte\Compiler\Nodes\Php\ExpressionNode;
use Latte\Compiler\Nodes\Php\Scalar\StringNode;
class ConfirmNode extends StatementNode
{
public ExpressionNode $message;
public static function create(Tag $tag): self
{
$tag->expectArguments();
$node = $tag->node = new self;
$node->message = $tag->parser->parseExpression();
return $node;
}
/**
* Doğru kaçış ile 'onclick' nitelik kodunu oluşturur.
*/
public function print(PrintContext $context): string
{
// Hem JavaScript hem de HTML nitelik bağlamları için doğru kaçışı sağlar.
return $context->format(
<<<'XX'
echo ' onclick="', LR\Filters::escapeHtmlAttr('return confirm(' . LR\Filters::escapeJs(%node) . ')'), '"' %line;
XX,
$this->message,
$this->position,
);
}
public function &getIterator(): \Generator
{
yield $this->message;
}
}
print()
metodu, şablon oluşturma sırasında sonunda onclick="..."
HTML niteliğini yazdıracak PHP
kodunu oluşturur. İç içe geçmiş bağlamların (HTML niteliği içindeki JavaScript) işlenmesi dikkatli kaçış gerektirir.
LR\Filters::escapeJs(%node)
filtresi çalışma zamanında çağrılır ve mesajı JavaScript içinde kullanım için
doğru şekilde kaçar (çıktı "Sure?"
gibi olurdu). Ardından LR\Filters::escapeHtmlAttr(...)
filtresi, HTML niteliklerinde özel olan karakterleri kaçar, böylece çıktıyı
return confirm("Sure?")
olarak değiştirir. Bu iki aşamalı çalışma zamanı kaçışı,
mesajın JavaScript için güvenli olmasını ve sonuçtaki JavaScript kodunun bir HTML onclick
niteliğine eklenmek
için güvenli olmasını sağlar.
Kayıt ve kullanım
n:niteliğini uzantınızda kaydedin. Anahtardaki n:
önekini unutmayın:
class MyLatteExtension extends Extension
{
public function getTags(): array
{
return [
'datetime' => DatetimeNode::create(...),
'debug' => DebugNode::create(...),
'repeat' => RepeatNode::create(...),
'n:confirm' => ConfirmNode::create(...), // n:confirm'i kaydet
];
}
}
Şimdi n:confirm
'i bağlantılarda, düğmelerde veya form öğelerinde kullanabilirsiniz:
<a href="delete.php?id=123" n:confirm='"{$id} öğesini gerçekten silmek istiyor musunuz?"'>Sil</a>
Oluşturulan HTML:
<a href="delete.php?id=123" onclick="return confirm("Öğe 123'ü gerçekten silmek istiyor musunuz?")">Sil</a>
Kullanıcı bağlantıya tıkladığında, tarayıcı onclick
kodunu yürütür, onay iletişim kutusunu
görüntüler ve yalnızca kullanıcı "Tamam"ı tıklarsa delete.php
'ye gider.
Bu örnek, print()
metodunda uygun PHP kodunu oluşturarak ana HTML öğesinin davranışını veya niteliklerini
değiştirmek için saf bir n:niteliğin nasıl oluşturulabileceğini gösterir. Genellikle gerekli olan çift kaçışı
unutmayın: biri hedef bağlam için (bu durumda JavaScript) ve diğeri HTML nitelik bağlamı için.
Gelişmiş konular
Önceki bölümler temel kavramları kapsarken, özel Latte etiketleri oluştururken karşılaşabileceğiniz birkaç gelişmiş konu aşağıdadır.
Etiket çıktı modları
create()
fonksiyonunuza iletilen Tag
nesnesinin bir outputMode
özelliği vardır. Bu
özellik, Latte'nin çevreleyen boşlukları ve girintiyi nasıl ele aldığını etkiler, özellikle etiket kendi satırında
kullanıldığında. Bu özelliği create()
fonksiyonunuzda değiştirebilirsiniz.
Tag::OutputKeepIndentation
({=...}
gibi çoğu etiket için varsayılan): Latte, etiketten önceki girintiyi korumaya çalışır. Etiketten sonraki yeni satırlar genellikle korunur. Bu, satır içi içerik yazdıran etiketler için uygundur.Tag::OutputRemoveIndentation
({if}
,{foreach}
gibi blok etiketleri için varsayılan): Latte, baştaki girintiyi ve potansiyel olarak bir sonraki yeni satırı kaldırır. Bu, oluşturulan PHP kodunu daha temiz tutmaya yardımcı olur ve etiketin kendisinden kaynaklanan HTML çıktısındaki ek boş satırları önler. Kontrol yapılarını veya kendileri boşluk eklememesi gereken blokları temsil eden etiketler için bunu kullanın.Tag::OutputNone
({var}
,{default}
gibi etiketler tarafından kullanılır):RemoveIndentation
'a benzer, ancak etiketin kendisinin doğrudan çıktı üretmediğini daha güçlü bir şekilde işaret eder, potansiyel olarak etrafındaki boşlukların işlenmesini daha agresif bir şekilde etkiler. Bildirimsel veya ayarlama etiketleri için uygundur.
Etiketinizin amacına en uygun modu seçin. Çoğu yapısal veya kontrol etiketi için OutputRemoveIndentation
genellikle uygundur.
Üst/en yakın etiketlere erişim
Bazen bir etiketin davranışı, kullanıldığı bağlama, özellikle içinde bulunduğu üst etikete/etiketlere bağlı
olması gerekir. create()
fonksiyonunuza iletilen Tag
nesnesi, tam olarak bu amaç için bir
closestTag(array $classes, ?callable $condition = null): ?Tag
metodu sağlar.
Bu metot, mevcut açık etiketlerin hiyerarşisinde (ayrıştırma sırasında dahili olarak temsil edilen HTML öğeleri
dahil) yukarı doğru arama yapar ve belirli ölçütlere uyan en yakın atanın Tag
nesnesini döndürür. Eşleşen
bir ata bulunmazsa null
döndürür.
$classes
dizisi, ne tür ata etiketleri aradığınızı belirtir. Ata etiketin ilişkili düğümünün
($ancestorTag->node
) bu sınıfın bir örneği olup olmadığını kontrol eder.
function create(Tag $tag)
{
// Düğümü ForeachNode örneği olan en yakın ata etiketini arayın
$foreachTag = $tag->closestTag([ForeachNode::class]);
if ($foreachTag) {
// ForeachNode örneğine kendisi erişebiliriz:
$foreachNode = $foreachTag->node;
}
}
$foreachTag->node
'a dikkat edin: Bu yalnızca, Latte etiket geliştirmede, oluşturulan düğümü
create()
metodu içinde hemen $tag->node
'a atamanın bir kuralı olduğu için çalışır, her zaman
yaptığımız gibi.
Bazen yalnızca düğüm türünü karşılaştırmak yeterli olmaz. Potansiyel bir ata etiketin veya düğümünün belirli
bir özelliğini kontrol etmeniz gerekebilir. closestTag()
için isteğe bağlı ikinci argüman, potansiyel ata
Tag
nesnesini kabul eden ve geçerli bir eşleşme olup olmadığını döndürmesi gereken bir
çağrılabilirdir.
function create(Tag $tag)
{
$dynamicBlockTag = $tag->closestTag(
[BlockNode::class],
// Koşul: blok dinamik olmalıdır
fn(Tag $blockTag) => $blockTag->node->block->isDynamic(),
);
}
closestTag()
kullanmak, bağlama duyarlı olan ve şablon yapınız içinde doğru kullanımı zorlayan etiketler
oluşturmanıza olanak tanır, bu da daha sağlam ve anlaşılır şablonlara yol açar.
PrintContext::format()
yer tutucuları
Düğümlerimizin print()
metotlarında PHP kodu oluşturmak için sık sık PrintContext::format()
kullandık. Bir maske dizesi ve maske içindeki yer tutucuları değiştiren sonraki argümanları kabul eder. Mevcut yer
tutucuların bir özeti aşağıdadır:
%node
: Argüman birNode
örneği olmalıdır. Düğümünprint()
metodunu çağırır ve sonuçtaki PHP kod dizesini ekler.%dump
: Argüman herhangi bir PHP değeridir. Değeri geçerli PHP koduna dışa aktarır. Skalerler, diziler, null için uygundur.$context->format('echo %dump;', 'Hello')
→echo 'Hello';
$context->format('$arr = %dump;', [1, 2])
→$arr = [1, 2];
%raw
: Argümanı herhangi bir kaçış veya değişiklik olmadan doğrudan çıktı PHP koduna ekler. Dikkatli kullanın, öncelikle önceden oluşturulmuş PHP kod parçacıklarını veya değişken adlarını eklemek için.$context->format('%raw = 1;', '$variableName')
→$variableName = 1;
%args
: Argüman birExpression\ArrayNode
olmalıdır. Dizi öğelerini bir fonksiyon veya metot çağrısı için argümanlar olarak biçimlendirilmiş şekilde yazdırır (virgülle ayrılmış, varsa adlandırılmış argümanları işler).$argsNode = new ArrayNode([...]);
$context->format('myFunc(%args);', $argsNode)
→myFunc(1, name: 'Joe');
%line
: Argüman birPosition
nesnesi olmalıdır (genellikle$this->position
). Kaynak satır numarasını gösteren bir PHP yorumu/* line X */
ekler.$context->format('echo "Hi" %line;', $this->position)
→echo "Hi" /* line 42 */;
%escape(...)
: Çalışma zamanında iç ifadeyi mevcut bağlama duyarlı kaçış kurallarını kullanarak kaçan PHP kodu oluşturur.$context->format('echo %escape(%node);', $variableNode)
%modify(...)
: Argüman birModifierNode
olmalıdır.ModifierNode
'da belirtilen filtreleri iç içeriğe uygulayan PHP kodu oluşturur,|noescape
ile devre dışı bırakılmadıkça bağlama duyarlı kaçış dahil.$context->format('%modify(%node);', $modifierNode, $variableNode)
%modifyContent(...)
:%modify
'a benzer, ancak yakalanan içerik bloklarını (genellikle HTML) değiştirmek için tasarlanmıştır.
Argümanlara dizinlerine göre (sıfırdan başlayarak) açıkça başvurabilirsiniz: %0.node
,
%1.dump
, %2.raw
, vb. Bu, bir argümanı format()
'a tekrar tekrar iletmeden maske içinde
birkaç kez yeniden kullanmanızı sağlar. %0.raw
ve %2.raw
'ın kullanıldığı {repeat}
etiketi örneğine bakın.
Karmaşık argüman ayrıştırma örneği
parseExpression()
, parseArguments()
vb. birçok durumu kapsarken, bazen
$tag->parser->stream
aracılığıyla erişilebilen daha düşük seviyeli TokenStream
kullanan
daha karmaşık ayrıştırma mantığına ihtiyacınız olur.
Hedef: {embedYoutube $videoID, width: 640, height: 480}
etiketini oluşturmak. Gerekli video ID'sini (dize
veya değişken) ve ardından boyutlar için isteğe bağlı anahtar-değer çiftlerini ayrıştırmak istiyoruz.
<?php
namespace App\Latte;
class YoutubeNode extends StatementNode
{
public ExpressionNode $videoId;
public ?ExpressionNode $width = null;
public ?ExpressionNode $height = null;
public static function create(Tag $tag): self
{
$tag->expectArguments();
$node = $tag->node = new self;
// Gerekli video ID'sini ayrıştırın
$node->videoId = $tag->parser->parseExpression();
// İsteğe bağlı anahtar-değer çiftlerini ayrıştırın
$stream = $tag->parser->stream; // Token akışını alın
while ($stream->tryConsume(',')) { // Virgülle ayırma gerektirir
// 'width' veya 'height' tanımlayıcısını bekleyin
$keyToken = $stream->consume(Token::Php_Identifier);
$key = strtolower($keyToken->text);
$stream->consume(':'); // İki nokta üst üste ayırıcısını bekleyin
$value = $tag->parser->parseExpression(); // Değer ifadesini ayrıştırın
if ($key === 'width') {
$node->width = $value;
} elseif ($key === 'height') {
$node->height = $value;
} else {
throw new CompileException("Bilinmeyen argüman '$key'. 'width' veya 'height' bekleniyordu.", $keyToken->position);
}
}
return $node;
}
}
Bu kontrol seviyesi, token akışıyla doğrudan etkileşim kurarak özel etiketleriniz için çok özel ve karmaşık sözdizimleri tanımlamanıza olanak tanır.
AuxiliaryNode
kullanma
Latte, kod oluşturma sırasında veya derleme geçişleri içinde özel durumlar için genel “yardımcı” düğümler
sağlar. Bunlar AuxiliaryNode
ve Php\Expression\AuxiliaryNode
'dur.
AuxiliaryNode
'u, temel işlevlerini – kod oluşturma ve alt düğümleri açığa çıkarma – yapıcısında
sağlanan argümanlara devreden esnek bir kapsayıcı düğüm olarak düşünün:
print()
delegasyonu: Yapıcının ilk argümanı bir PHP closure'dır. Latte,AuxiliaryNode
üzerindeprint()
metodunu çağırdığında, bu sağlanan closure'ı yürütür. Closure, birPrintContext
ve yapıcının ikinci argümanında iletilen herhangi bir düğümü kabul eder, bu da çalışma zamanında tamamen özel PHP kod oluşturma mantığı tanımlamanıza olanak tanır.getIterator()
delegasyonu: Yapıcının ikinci argümanıNode
nesneleri dizisidir. Latte'ninAuxiliaryNode
'un alt öğelerini (ör. derleme geçişleri sırasında) dolaşması gerektiğinde,getIterator()
metodu basitçe bu dizide listelenen düğümleri sağlar.
Örnek:
$node = new AuxiliaryNode(
// 1. Bu closure, print() gövdesi olur
fn(PrintContext $context, $arg1, $arg2) => $context->format('...%node...%node...', $arg1, $arg2),
// 2. Bu düğümler getIterator() metodu tarafından sağlanır ve yukarıdaki closure'a iletilir
[$argumentNode1, $argumentNode2]
);
Latte, oluşturulan kodu nereye eklemeniz gerektiğine bağlı olarak iki farklı tür sağlar:
Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode
: Bir ifadeyi temsil eden bir PHP kod parçası oluşturmanız gerektiğinde bunu kullanınLatte\Compiler\Nodes\AuxiliaryNode
: Bir veya daha fazla deyimi temsil eden bir PHP kod bloğu eklemeniz gerektiğinde daha genel amaçlar için bunu kullanın
print()
metodunuz veya derleme geçişiniz içinde standart düğümler ( StaticMethodCallNode
gibi)
yerine AuxiliaryNode
kullanmanın önemli bir nedeni, sonraki derleme geçişleri için görünürlük
kontrolüdür, özellikle Sandbox gibi güvenlikle ilgili olanlar.
Bir senaryo düşünün: Derleme geçişinizin, kullanıcı tarafından sağlanan bir ifadeyi ($userExpr
) belirli,
güvenilir bir yardımcı fonksiyon myInternalSanitize($userExpr)
çağrısıyla sarmalaması gerekir. Standart bir
new FunctionCallNode('myInternalSanitize', [$userExpr])
düğümü oluşturursanız, AST geçişine tamamen
görünür olacaktır. Sandbox geçişi daha sonra çalışırsa ve myInternalSanitize
izin verilenler listesinde
değilse, Sandbox bu çağrıyı engelleyebilir veya değiştirebilir, potansiyel olarak etiketinizin dahili
mantığını bozabilir, siz, etiket yazarı, bu özel çağrının güvenli ve gerekli olduğunu bilseniz bile. Bu nedenle,
çağrıyı doğrudan AuxiliaryNode
closure'ı içinde oluşturabilirsiniz.
use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode;
// ... print() veya derleme geçişi içinde ...
$wrappedNode = new AuxiliaryNode(
fn(PrintContext $context, $userExpr) => $context->format(
'myInternalSanitize(%node)', // Doğrudan PHP kodu oluşturun
$userExpr,
),
// ÖNEMLİ: Orijinal kullanıcı ifadesi düğümünü hala buraya iletin!
[$userExpr],
);
Bu durumda, Sandbox geçişi AuxiliaryNode
'u görür, ancak closure'ı tarafından oluşturulan PHP kodunu
analiz etmez. Closure içinde oluşturulan myInternalSanitize
çağrısını doğrudan engelleyemez.
Oluşturulan PHP kodunun kendisi geçişlerden gizlenirken, bu koda girdiler (kullanıcı verilerini veya ifadelerini
temsil eden düğümler) hala geçilebilir olmalıdır. Bu nedenle, AuxiliaryNode
yapıcısının ikinci
argümanı çok önemlidir. Closure'ınızın kullandığı tüm orijinal düğümleri (yukarıdaki örnekteki
$userExpr
gibi) içeren diziyi iletmelisiniz. AuxiliaryNode
'un getIterator()
'ı
bu düğümleri sağlayacaktır, Sandbox gibi derleme geçişlerinin potansiyel sorunlar için bunları analiz etmesine
olanak tanır.
En iyi uygulamalar
- Net amaç: Etiketinizin net ve gerekli bir amacı olduğundan emin olun. Filtreler veya fonksiyonlar ile kolayca çözülebilecek görevler için etiketler oluşturmayın.
getIterator()
'ı doğru şekilde uygulayın: Her zamangetIterator()
'ı uygulayın ve şablondan ayrıştırılan tüm alt düğümlere (argümanlar, içerik) referanslar (&
) sağlayın. Bu, derleme geçişleri, güvenlik (Sandbox) ve potansiyel gelecek optimizasyonları için gereklidir.- Düğümler için genel özellikler: Alt düğümleri içeren özellikleri genel yapın, böylece derleme geçişleri gerekirse bunları değiştirebilir.
PrintContext::format()
kullanın: PHP kodu oluşturmak içinformat()
metodunu kullanın. Tırnak işaretlerini işler, yer tutucuları doğru şekilde kaçar ve satır numarası yorumlarını otomatik olarak ekler.- Geçici değişkenler (
$__
): Geçici değişkenlere ihtiyaç duyan çalışma zamanı PHP kodu oluştururken (ör. ara toplamları, döngü sayaçlarını saklamak için), kullanıcı değişkenleri ve Latte'nin dahili$ʟ_
değişkenleriyle çakışmaları önlemek için$__
önek kuralını kullanın. - İç içe geçme ve benzersiz ID'ler: Etiketiniz iç içe geçebiliyorsa veya çalışma zamanında örneğe özgü
duruma ihtiyaç duyuyorsa, geçici
$__
değişkenleriniz için benzersiz sonekler oluşturmak üzereprint()
metodunuz içinde$context->generateId()
kullanın. - Harici veriler için sağlayıcılar: Çalışma zamanı verilerine veya hizmetlerine ($this->global->…)
erişmek için değerleri sabit kodlamak veya genel duruma güvenmek yerine sağlayıcıları (
Extension::getProviders()
aracılığıyla kaydedilir) kullanın. Sağlayıcı adları için üretici önekleri kullanın. - n:nitelikleri düşünün: Eşli etiketiniz mantıksal olarak tek bir HTML öğesi üzerinde çalışıyorsa, Latte
muhtemelen otomatik
n:nitelik
desteği sağlar. Kullanıcı rahatlığı için bunu aklınızda bulundurun. Bir nitelik değiştirme etiketi oluşturuyorsanız, saf birn:nitelik
'in en uygun biçim olup olmadığını düşünün. - Test etme: Etiketleriniz için testler yazın, hem farklı sözdizimsel girdilerin ayrıştırılmasını hem de oluşturulan PHP kodunun çıktısının doğruluğunu kapsayın.
Bu yönergeleri izleyerek, Latte şablonlama motoruyla sorunsuz bir şekilde bütünleşen güçlü, sağlam ve sürdürülebilir özel etiketler oluşturabilirsiniz.
Latte'nin bir parçası olan düğüm sınıflarını incelemek, ayrıştırma sürecinin tüm ayrıntılarını öğrenmenin en iyi yoludur.