Derleme Geçişleri
Derleme geçişleri, Latte şablonlarını soyut sözdizimi ağacına (AST) ayrıştırıldıktan sonra ve son PHP kodu oluşturulmadan önce analiz etmek ve değiştirmek için güçlü bir mekanizma sağlar. Bu, şablonların gelişmiş manipülasyonunu, optimizasyonları, güvenlik kontrollerini (Sandbox gibi) ve şablonlar hakkında bilgi toplamayı mümkün kılar. Bu kılavuz, kendi derleme geçişlerinizi oluşturma konusunda size yol gösterecektir.
Derleme Geçişi Nedir?
Derleme geçişlerinin rolünü anlamak için Latte derleme sürecine göz atın. Gördüğünüz gibi, derleme geçişleri kritik bir aşamada çalışır ve ilk ayrıştırma ile son kod çıktısı arasında derin müdahaleye izin verir.
Özünde, bir derleme geçişi basitçe tek bir argüman alan bir PHP çağrılabilir nesnesidir (fonksiyon, statik metot veya
örnek metodu gibi): şablonun kök AST düğümü, ki bu her zaman Latte\Compiler\Nodes\TemplateNode
örneğidir.
Bir derleme geçişinin birincil amacı genellikle aşağıdakilerden biri veya her ikisidir:
- Analiz: AST'yi dolaşarak şablon hakkında bilgi toplamak (örneğin, tanımlanmış tüm blokları bulmak, belirli etiketlerin kullanımını kontrol etmek, belirli güvenlik kısıtlamalarının karşılandığından emin olmak).
- Değişiklik: AST'nin yapısını veya düğümlerin niteliklerini değiştirmek (örneğin, otomatik olarak HTML nitelikleri eklemek, belirli etiket kombinasyonlarını optimize etmek, kullanımdan kaldırılmış etiketleri yenileriyle değiştirmek, sanal alan kurallarını uygulamak).
Kayıt
Derleme geçişleri, uzantının
getPasses()
yöntemi kullanılarak kaydedilir. Bu yöntem, anahtarların benzersiz geçiş adları (dahili olarak ve
sıralama için kullanılır) ve değerlerin geçiş mantığını uygulayan PHP çağrılabilir nesneleri olduğu ilişkisel bir
dizi döndürür.
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Extension;
class MyExtension extends Extension
{
public function getPasses(): array
{
return [
'modificationPass' => $this->modifyTemplateAst(...),
// ... diğer geçişler ...
];
}
public function modifyTemplateAst(TemplateNode $templateNode): void
{
// Uygulama...
}
}
Latte'nin temel uzantıları ve kendi uzantılarınız tarafından kaydedilen geçişler sırayla çalışır. Sıra önemli
olabilir, özellikle bir geçiş başka bir geçişin sonuçlarına veya değişikliklerine bağlıysa. Latte, gerekirse bu
sırayı kontrol etmek için yardımcı bir mekanizma sağlar; ayrıntılar için Extension::getPasses()
dokümantasyonuna bakın.
AST Örneği
AST hakkında daha iyi bir fikir vermek için bir örnek ekliyoruz. Bu kaynak şablondur:
{foreach $category->getItems() as $item}
<li>{$item->name|upper}</li>
{else}
öğe bulunamadı
{/foreach}
Ve bu onun AST biçimindeki temsilidir:
Latte\Compiler\Nodes\TemplateNode( Latte\Compiler\Nodes\FragmentNode( - Latte\Essential\Nodes\ForeachNode( expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode( object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category') name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems') ) value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') content: Latte\Compiler\Nodes\FragmentNode( - Latte\Compiler\Nodes\TextNode(' ') - Latte\Compiler\Nodes\Html\ElementNode('li')( content: Latte\Essential\Nodes\PrintNode( expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') name: Latte\Compiler\Nodes\Php\IdentifierNode('name') ) modifier: Latte\Compiler\Nodes\Php\ModifierNode( filters: - Latte\Compiler\Nodes\Php\FilterNode('upper') ) ) ) ) else: Latte\Compiler\Nodes\FragmentNode( - Latte\Compiler\Nodes\TextNode('öğe bulunamadı') ) ) ) )
NodeTraverser
ile AST'yi Dolaşma
Karmaşık AST yapısını dolaşmak için özyinelemeli fonksiyonları manuel olarak yazmak sıkıcı ve hataya açıktır. Latte bu amaç için özel bir araç sağlar: Latte\Compiler\NodeTraverser. Bu sınıf, Ziyaretçi tasarım desenini uygular, bu da AST dolaşımını sistematik ve yönetimi kolay hale getirir.
Temel kullanım, bir NodeTraverser
örneği oluşturmayı ve traverse()
yöntemini çağırmayı,
AST'nin kök düğümünü ve bir veya iki “ziyaretçi” çağrılabilir nesnesini geçirmeyi içerir:
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes;
(new NodeTraverser)->traverse(
$templateNode,
// 'enter' ziyaretçisi: Düğüme girerken çağrılır (çocuklarından önce)
enter: function (Node $node) {
echo "Düğüm türüne giriş: " . $node::class . "\n";
// Burada düğümü inceleyebilirsiniz
if ($node instanceof Nodes\TextNode) {
// echo "Metin bulundu: " . $node->content . "\n";
}
},
// 'leave' ziyaretçisi: Düğümden çıkarken çağrılır (çocuklarından sonra)
leave: function (Node $node) {
echo "Düğüm türünden çıkış: " . $node::class . "\n";
// Burada çocukları işledikten sonra eylemler gerçekleştirebilirsiniz
},
);
İhtiyaçlarınıza bağlı olarak yalnızca enter
ziyaretçisini, yalnızca leave
ziyaretçisini
veya her ikisini de sağlayabilirsiniz.
enter(Node $node)
: Bu fonksiyon, dolaşan bu düğümün herhangi bir çocuğunu ziyaret etmeden
önce her düğüm için yürütülür. Şunlar için kullanışlıdır:
- Ağaçta aşağı doğru ilerlerken bilgi toplama.
- Çocukları işlemeden önce karar verme (onları atlama kararı gibi, bkz. Dolaşımı Optimize Etme).
- Çocukları ziyaret etmeden önce düğümü potansiyel olarak değiştirme (daha az yaygın).
leave(Node $node)
: Bu fonksiyon, tüm çocukları (ve tüm alt ağaçları) tamamen ziyaret edildikten (hem
giriş hem de çıkış) sonra her düğüm için yürütülür. Şunlar için en yaygın yerdir:
Hem enter
hem de leave
ziyaretçileri, dolaşım sürecini etkilemek için isteğe bağlı olarak bir
değer döndürebilir. null
(veya hiçbir şey) döndürmek dolaşıma normal şekilde devam eder, bir
Node
örneği döndürmek geçerli düğümü değiştirir ve NodeTraverser::RemoveNode
veya
NodeTraverser::StopTraversal
gibi özel sabitleri döndürmek, sonraki bölümlerde açıklandığı gibi akışı
değiştirir.
Dolaşım Nasıl Çalışır
NodeTraverser
dahili olarak, her Node
sınıfının uygulaması gereken getIterator()
yöntemini kullanır (Özel Etiketler
Oluşturma bölümünde tartışıldığı gibi). getIterator()
aracılığıyla elde edilen çocuklar üzerinde
yinelenir, üzerlerinde özyinelemeli olarak traverse()
çağırır ve enter
ve leave
ziyaretçilerinin, yineleyiciler aracılığıyla erişilebilen ağaçtaki her düğüm için doğru derinlik öncelikli sırada
çağrılmasını sağlar. Bu, özel etiket düğümlerinizde doğru şekilde uygulanan getIterator()
'ın derleme
geçişlerinin doğru çalışması için neden kesinlikle gerekli olduğunu tekrar vurgular.
Şablonda {do}
etiketinin (Latte\Essential\Nodes\DoNode
ile temsil edilir) kaç kez
kullanıldığını sayan basit bir geçiş yazalım.
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Essential\Nodes\DoNode;
function countDoTags(TemplateNode $templateNode): void
{
$count = 0;
(new NodeTraverser)->traverse(
$templateNode,
enter: function (Node $node) use (&$count): void {
if ($node instanceof DoNode) {
$count++;
}
},
// 'leave' ziyaretçisi bu görev için gerekli değil
);
echo "{do} etiketi $count kez bulundu.\n";
}
$latte = new Latte\Engine;
$ast = $latte->parse($templateSource);
countDoTags($ast);
Bu örnekte, ziyaret edilen her düğümün türünü kontrol etmek için yalnızca enter
ziyaretçisine
ihtiyacımız vardı.
Ardından, bu ziyaretçilerin AST'yi gerçekte nasıl değiştirdiğini inceleyeceğiz.
AST'yi Değiştirme
Derleme geçişlerinin ana amaçlarından biri soyut sözdizimi ağacını değiştirmektir. Bu, PHP kodu oluşturulmadan önce
doğrudan şablon yapısı üzerinde güçlü dönüşümler, optimizasyonlar veya kural uygulamalarına olanak tanır.
NodeTraverser
, enter
ve leave
ziyaretçileri içinde bunu başarmanın birkaç
yolunu sunar.
Önemli Not: AST'yi değiştirmek dikkat gerektirir. Yanlış değişiklikler – temel düğümleri kaldırmak veya bir düğümü uyumsuz bir türle değiştirmek gibi – kod oluşturma sırasında hatalara yol açabilir veya çalışma zamanında beklenmedik davranışlara neden olabilir. Değişiklik geçişlerinizi her zaman kapsamlı bir şekilde test edin.
Düğüm Özelliklerini Değiştirme
Ağacı değiştirmenin en basit yolu, dolaşım sırasında ziyaret edilen düğümlerin genel özelliklerini doğrudan değiştirmektir. Tüm düğümler, ayrıştırılmış argümanlarını, içeriklerini veya niteliklerini genel özelliklerde saklar.
Örnek: Tüm statik metin düğümlerini (TextNode
, Latte etiketleri dışındaki normal HTML veya metni
temsil eder) bulan ve içeriklerini doğrudan AST'de büyük harflere dönüştüren bir geçiş oluşturalım.
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Compiler\Nodes\TextNode;
function uppercaseStaticText(TemplateNode $templateNode): void
{
(new NodeTraverser)->traverse(
$templateNode,
// TextNode'un işlenecek çocuğu olmadığı için 'enter' kullanabiliriz
enter: function (Node $node) {
// Bu düğüm statik bir metin bloğu mu?
if ($node instanceof TextNode) {
// Evet! Doğrudan genel 'content' özelliğini değiştiriyoruz.
$node->content = mb_strtoupper(html_entity_decode($node->content));
}
// Hiçbir şey döndürmeye gerek yok; değişiklik doğrudan uygulanır.
},
);
}
Bu örnekte, enter
ziyaretçisi geçerli $node
'un TextNode
türünde olup olmadığını
kontrol eder. Eğer öyleyse, genel $content
özelliğini doğrudan mb_strtoupper()
kullanarak
güncelleriz. Bu, PHP kodu oluşturulmadan önce AST'de depolanan statik metnin içeriğini doğrudan değiştirir. Nesneyi
doğrudan değiştirdiğimiz için ziyaretçiden hiçbir şey döndürmemiz gerekmez.
Etki: Şablon <p>Merhaba</p>{= $var }<span>Dünya</span>
içeriyorsa, bu geçişten sonra
AST şuna benzer bir şeyi temsil edecektir: <p>MERHABA</p>{= $var }<span>DÜNYA</span>
. Bu,
$var'ın içeriğini ETKİLEMEZ.
Düğümleri Değiştirme
Daha güçlü bir değişiklik tekniği, bir düğümü tamamen başka bir düğümle değiştirmektir. Bu, enter
veya leave
ziyaretçisinden yeni bir Node
örneği döndürerek yapılır.
NodeTraverser
daha sonra orijinal düğümü, ebeveyn düğümünün yapısında döndürülen düğümle
değiştirir.
Örnek: PHP_VERSION
sabitinin (ConstantFetchNode
ile temsil edilir) tüm kullanımlarını
bulan ve bunları derleme sırasında algılanan gerçek PHP sürümünü içeren bir dize literali
(StringNode
) ile doğrudan değiştiren bir geçiş oluşturalım. Bu, derleme zamanı optimizasyonunun bir
biçimidir.
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Compiler\Nodes\Php\Expression\ConstantFetchNode;
use Latte\Compiler\Nodes\Php\Scalar\StringNode;
function inlinePhpVersion(TemplateNode $templateNode): void
{
(new NodeTraverser)->traverse(
$templateNode,
// 'leave' genellikle değiştirme için kullanılır, çocukların (varsa)
// önce işlenmesini sağlar, ancak burada 'enter' da çalışırdı.
leave: function (Node $node) {
// Bu düğüm bir sabit erişimi mi ve sabit adı 'PHP_VERSION' mu?
if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') {
// Geçerli PHP sürümünü içeren yeni bir StringNode oluşturuyoruz
$newNode = new StringNode(PHP_VERSION);
// İsteğe bağlı, ancak iyi bir uygulama: konum bilgilerini kopyalayın
$newNode->position = $node->position;
// Yeni StringNode'u döndürüyoruz. Traverser,
// orijinal ConstantFetchNode'u bu $newNode ile değiştirecektir.
return $newNode;
}
// Eğer bir Node döndürmezsek, orijinal $node korunur.
},
);
}
Burada leave
ziyaretçisi, PHP_VERSION
için belirli bir ConstantFetchNode
'u tanımlar.
Ardından, derleme zamanında PHP_VERSION
sabitinin değerini içeren tamamen yeni bir
StringNode
oluşturur. Bu $newNode
'u döndürerek, traverser'a orijinal
ConstantFetchNode
'u AST'de değiştirmesini söyler.
Etki: Şablon {= PHP_VERSION }
içeriyorsa ve derleme PHP 8.2.1 üzerinde çalışıyorsa, bu geçişten sonraki
AST etkili bir şekilde {= '8.2.1' }
temsil edecektir.
Değiştirme için enter
vs. leave
seçimi:
- Yeni düğümü oluşturmak, eski düğümün çocuklarını işlemenin sonuçlarına bağlıysa veya çocukların
değiştirilmeden önce ziyaret edilmesini basitçe sağlamak istiyorsanız (yaygın uygulama)
leave
kullanın. - Bir düğümü çocukları hiç ziyaret edilmeden önce değiştirmek istiyorsanız
enter
kullanın.
Düğümleri Kaldırma
Ziyaretçiden özel NodeTraverser::RemoveNode
sabitini döndürerek bir düğümü AST'den tamamen
kaldırabilirsiniz.
Örnek: Latte çekirdeği tarafından oluşturulan AST'de CommentNode
ile temsil edilen tüm şablon
yorumlarını ({* ... *}
) kaldıralım (genellikle daha önce işlense de, bu bir örnek olarak hizmet eder).
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Compiler\Nodes\CommentNode;
function removeCommentNodes(TemplateNode $templateNode): void
{
(new NodeTraverser)->traverse(
$templateNode,
// Yorumu kaldırmak için çocuk bilgisine ihtiyacımız olmadığı için 'enter' burada uygundur
enter: function (Node $node) {
if ($node instanceof CommentNode) {
// Traverser'a bu düğümü AST'den kaldırmasını işaret ediyoruz
return NodeTraverser::RemoveNode;
}
},
);
}
Uyarı: RemoveNode
'u dikkatli kullanın. Temel içeriği içeren veya yapıyı etkileyen bir düğümü
kaldırmak (bir döngünün içerik düğümünü kaldırmak gibi), bozuk şablonlara veya geçersiz oluşturulmuş koda yol
açabilir. Gerçekten isteğe bağlı veya bağımsız olan düğümler (yorumlar veya hata ayıklama etiketleri gibi) veya boş
yapısal düğümler (örneğin, boş bir FragmentNode
bazı bağlamlarda bir temizleme geçişi tarafından güvenle
kaldırılabilir) için en güvenlidir.
Bu üç yöntem – özellikleri değiştirme, düğümleri değiştirme ve düğümleri kaldırma – derleme geçişleriniz içinde AST'yi manipüle etmek için temel araçları sağlar.
Dolaşımı Optimize Etme
Şablonların AST'si oldukça büyük olabilir ve potansiyel olarak binlerce düğüm içerebilir. Geçişiniz yalnızca
ağacın belirli bölümleriyle ilgileniyorsa, her bir düğümü dolaşmak gereksiz olabilir ve derleme performansını
etkileyebilir. NodeTraverser
, dolaşımı optimize etmenin yollarını sunar:
Çocukları Atlama
Belirli bir düğüm türüne rastladığınızda, torunlarından hiçbirinin aradığınız düğümleri içeremeyeceğini
biliyorsanız, traverser'a çocuklarını ziyaret etmeyi atlamasını söyleyebilirsiniz. Bu, enter
ziyaretçisinden NodeTraverser::DontTraverseChildren
sabitini döndürerek yapılır. Bu, dolaşım sırasında tüm
dalları atlar, potansiyel olarak önemli ölçüde zaman kazandırır, özellikle etiketler içinde karmaşık PHP ifadeleri olan
şablonlarda.
Dolaşımı Durdurma
Geçişinizin yalnızca bir şeyin ilk örneğini bulması gerekiyorsa (belirli bir düğüm türü, bir koşulun
karşılanması), bulduğunuz anda tüm dolaşım sürecini tamamen durdurabilirsiniz. Bu, enter
veya
leave
ziyaretçisinden NodeTraverser::StopTraversal
sabitini döndürerek başarılır.
traverse()
yöntemi başka herhangi bir düğümü ziyaret etmeyi durdurur. Potansiyel olarak çok büyük bir
ağaçta yalnızca ilk eşleşmeye ihtiyacınız varsa bu son derece etkilidir.
Kullanışlı Yardımcı NodeHelpers
NodeTraverser
ince ayarlı kontrol sunarken, Latte ayrıca birkaç yaygın arama ve analiz görevi için
NodeTraverser
'ı kapsayan pratik bir yardımcı sınıf olan Latte\Compiler\NodeHelpers'ı da sağlar ve
genellikle daha az standart kod gerektirir.
find (Node $startNode, callable $filter): array
Bu statik yöntem, $startNode
'dan başlayan (dahil) alt ağaçtaki $filter
geri çağrısını
karşılayan tüm düğümleri bulur. Eşleşen düğümlerin bir dizisini döndürür.
Örnek: Tüm şablondaki tüm değişken düğümlerini (VariableNode
) bulun.
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\Php\Expression\VariableNode;
use Latte\Compiler\Nodes\TemplateNode;
function findAllVariables(TemplateNode $templateNode): array
{
return NodeHelpers::find(
$templateNode,
fn($node) => $node instanceof VariableNode,
);
}
findFirst (Node $startNode, callable $filter): ?Node
find
'a benzer, ancak $filter
geri çağrısını karşılayan ilk düğümü bulduktan hemen
sonra dolaşımı durdurur. Bulunan Node
nesnesini veya eşleşen düğüm bulunamazsa null
döndürür.
Bu, esasen NodeTraverser::StopTraversal
etrafında pratik bir sarmalayıcıdır.
Örnek: {parameters}
düğümünü bulun (daha önceki manuel örnekle aynı, ancak daha kısa).
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\TemplateNode;
use Latte\Essential\Nodes\ParametersNode;
function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode
{
return NodeHelpers::findFirst(
$templateNode->head, // Verimlilik için yalnızca ana bölümde arama yapın
fn($node) => $node instanceof ParametersNode,
);
}
toValue (ExpressionNode $node, bool $constants = false): mixed
Bu statik yöntem, bir ExpressionNode
'u derleme zamanında değerlendirmeye çalışır ve karşılık
gelen PHP değerini döndürür. Yalnızca basit literal düğümler (StringNode
, IntegerNode
,
FloatNode
, BooleanNode
, NullNode
) ve yalnızca bu tür değerlendirilebilir öğeleri
içeren ArrayNode
örnekleri için güvenilir bir şekilde çalışır.
Eğer $constants
true
olarak ayarlanırsa, defined()
kontrol ederek ve
constant()
kullanarak ConstantFetchNode
ve ClassConstantFetchNode
'u çözmeye de
çalışacaktır.
Düğüm değişkenler, fonksiyon çağrıları veya diğer dinamik öğeler içeriyorsa, derleme zamanında değerlendirilemez
ve yöntem bir InvalidArgumentException
fırlatır.
Kullanım Durumu: Derleme zamanı kararları için derleme sırasında bir etiket argümanının statik değerini elde etme.
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\Php\ExpressionNode;
function getStaticStringArgument(ExpressionNode $argumentNode): ?string
{
try {
$value = NodeHelpers::toValue($argumentNode);
return is_string($value) ? $value : null;
} catch (\InvalidArgumentException $e) {
// Argüman statik bir literal dize değildi
return null;
}
}
toText (?Node $node): ?string
Bu statik yöntem, basit düğümlerden düz metin içeriğini çıkarmak için kullanışlıdır. Öncelikle şunlarla çalışır:
TextNode
:$content
'ini döndürür.FragmentNode
: Tüm çocukları içintoText()
sonucunu birleştirir. Herhangi bir çocuk metne dönüştürülemezse (örneğin, birPrintNode
içeriyorsa),null
döndürür.NopNode
: Boş bir dize döndürür.- Diğer düğüm türleri:
null
döndürür.
Kullanım Durumu: Bir derleme geçişi sırasında analiz için bir HTML niteliğinin veya basit bir HTML öğesinin statik metin içeriğini elde etme.
use Latte\Compiler\NodeHelpers;
use Latte\Compiler\Nodes\Html\AttributeNode;
function getStaticAttributeValue(AttributeNode $attr): ?string
{
// $attr->value tipik olarak bir AreaNode'dur (FragmentNode veya TextNode gibi)
return NodeHelpers::toText($attr->value);
}
// Geçişte kullanım örneği:
// if ($node instanceof Html\ElementNode && $node->name === 'meta') {
// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name'));
// if ($nameAttrValue === 'description') { ... }
// }
NodeHelpers
, yaygın AST dolaşım ve analiz görevleri için hazır çözümler sunarak derleme geçişlerinizi
basitleştirebilir.
Pratik Örnekler
Bazı pratik sorunları çözmek için AST dolaşım ve değişiklik kavramlarını uygulayalım. Bu örnekler, derleme geçişlerinde kullanılan yaygın desenleri gösterir.
<img>
'ye Otomatik Olarak loading="lazy"
Ekleme
Modern tarayıcılar, loading="lazy"
niteliğini kullanarak görüntüler için yerel geç yüklemeyi destekler.
Henüz bir loading
niteliği olmayan tüm <img>
etiketlerine bu niteliği otomatik olarak ekleyen
bir geçiş oluşturalım.
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes;
use Latte\Compiler\Nodes\Html;
function addLazyLoading(Nodes\TemplateNode $templateNode): void
{
(new NodeTraverser)->traverse(
$templateNode,
// Düğümü doğrudan değiştirdiğimiz ve bu karar için çocuklara
// bağlı olmadığımız için 'enter' kullanabiliriz.
enter: function (Node $node) {
// Bu 'img' adlı bir HTML öğesi mi?
if ($node instanceof Html\ElementNode && $node->name === 'img') {
// Nitelik düğümünün var olduğundan emin olun
$node->attributes ??= new Nodes\FragmentNode;
// Zaten bir 'loading' niteliğinin olup olmadığını kontrol edin (büyük/küçük harf duyarsız)
foreach ($node->attributes->children as $attrNode) {
if ($attrNode instanceof Html\AttributeNode
&& $attrNode->name instanceof Nodes\TextNode // Statik nitelik adı
&& strtolower($attrNode->name->content) === 'loading'
) {
return;
}
}
// Nitelikler boş değilse bir boşluk ekleyin
if ($node->attributes->children) {
$node->attributes->children[] = new Nodes\TextNode(' ');
}
// Yeni bir nitelik düğümü oluşturun: loading="lazy"
$node->attributes->children[] = new Html\AttributeNode(
name: new Nodes\TextNode('loading'),
value: new Nodes\TextNode('lazy'),
quote: '"',
);
// Değişiklik doğrudan nesnede uygulanır, hiçbir şey döndürmeye gerek yoktur.
}
},
);
}
Açıklama:
enter
ziyaretçisi,img
adlıHtml\ElementNode
düğümlerini arar.- Mevcut nitelikleri (
$node->attributes->children
) yineler veloading
niteliğinin zaten mevcut olup olmadığını kontrol eder. - Bulunamazsa,
loading="lazy"
temsil eden yeni birHtml\AttributeNode
oluşturur.
Fonksiyon Çağrılarını Kontrol Etme
Derleme geçişleri, Latte Sandbox'ın temelidir. Gerçek Sandbox karmaşık olsa da, yasaklanmış fonksiyon çağrılarını kontrol etmenin temel prensibini gösterebiliriz.
Amaç: Şablon ifadeleri içinde potansiyel olarak tehlikeli shell_exec
fonksiyonunun kullanımını
önlemek.
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
use Latte\Compiler\Nodes;
use Latte\Compiler\Nodes\Php;
use Latte\SecurityViolationException;
function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void
{
$forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Basit liste
$traverser = new NodeTraverser;
(new NodeTraverser)->traverse(
$templateNode,
enter: function (Node $node) use ($forbiddenFunctions) {
// Bu doğrudan bir fonksiyon çağrı düğümü mü?
if ($node instanceof Php\Expression\FunctionCallNode
&& $node->name instanceof Php\NameNode
&& isset($forbiddenFunctions[strtolower((string) $node->name)])
) {
throw new SecurityViolationException(
"{$node->name}() fonksiyonuna izin verilmiyor.",
$node->position,
);
}
},
);
}
Açıklama:
- Yasaklanmış fonksiyon adlarının bir listesini tanımlıyoruz.
enter
ziyaretçisiFunctionCallNode
'u kontrol eder.- Fonksiyon adı (
$node->name
) statik birNameNode
ise, küçük harfli dize temsilini yasaklı listemizle karşılaştırırız. - Yasaklanmış bir fonksiyon bulunursa, güvenlik kuralı ihlalini açıkça belirten ve derlemeyi durduran bir
Latte\SecurityViolationException
fırlatırız.
Bu örnekler, NodeTraverser
kullanarak derleme geçişlerinin, doğrudan şablonun AST yapısıyla etkileşime
girerek analiz, otomatik değişiklikler ve güvenlik kısıtlamalarının uygulanması için nasıl kullanılabileceğini
gösterir.
En İyi Uygulamalar
Derleme geçişleri yazarken, sağlam, sürdürülebilir ve verimli uzantılar oluşturmak için bu yönergeleri aklınızda bulundurun:
- Sıra Önemlidir: Geçişlerin çalıştığı sıranın farkında olun. Geçişiniz başka bir geçiş tarafından
oluşturulan AST yapısına (örneğin, Latte'nin temel geçişleri veya başka bir özel geçiş) bağlıysa veya diğer
geçişler sizin değişikliklerinize bağlı olabiliyorsa, bağımlılıkları tanımlamak için
Extension::getPasses()
tarafından sağlanan sıralama mekanizmasını kullanın (before
/after
). Ayrıntılar içinExtension::getPasses()
dokümantasyonuna bakın. - Tek Sorumluluk: İyi tanımlanmış tek bir görevi yerine getiren geçişler hedefleyin. Karmaşık dönüşümler için, mantığı birden fazla geçişe bölmeyi düşünün – belki biri analiz için, diğeri analiz sonuçlarına dayalı değişiklik için. Bu, netliği ve test edilebilirliği artırır.
- Performans: Derleme geçişlerinin şablon derleme süresine ekleme yaptığını unutmayın (genellikle şablon
değişene kadar yalnızca bir kez gerçekleşse de). Mümkünse geçişlerinizde hesaplama açısından pahalı işlemlerden
kaçının. AST'nin belirli bölümlerini ziyaret etmeniz gerekmediğini bildiğinizde
NodeTraverser::DontTraverseChildren
veNodeTraverser::StopTraversal
gibi dolaşım optimizasyonlarından yararlanın. NodeHelpers
Kullanın: Belirli düğümleri bulmak veya basit ifadeleri statik olarak değerlendirmek gibi yaygın görevler için, kendiNodeTraverser
mantığınızı yazmadan önceLatte\Compiler\NodeHelpers
'ın uygun bir yöntem sunup sunmadığını kontrol edin. Bu, zaman kazandırabilir ve standart kod miktarını azaltabilir.- Hata İşleme: Geçişiniz şablon AST'sinde bir hata veya geçersiz durum algılarsa, net bir mesaj ve ilgili
Position
nesnesiyle (genellikle$node->position
) birLatte\CompileException
(veya güvenlik sorunları içinLatte\SecurityViolationException
) fırlatın. Bu, şablon geliştiricisine yararlı geri bildirim sağlar. - İdempotans (Mümkünse): İdeal olarak, geçişinizi aynı AST üzerinde birden çok kez çalıştırmak, bir kez çalıştırmakla aynı sonucu üretmelidir. Bu her zaman mümkün değildir, ancak başarıldığında hata ayıklamayı ve geçiş etkileşimleri hakkında akıl yürütmeyi basitleştirir. Örneğin, değişiklik geçişinizin, değişikliği tekrar uygulamadan önce zaten uygulanıp uygulanmadığını kontrol ettiğinden emin olun.
Bu uygulamaları takip ederek, Latte'nin yeteneklerini güçlü ve güvenilir bir şekilde genişletmek için derleme geçişlerini etkili bir şekilde kullanabilir, daha güvenli, daha optimize edilmiş veya işlevsel olarak daha zengin şablon işlemeye katkıda bulunabilirsiniz.