Uzantı Oluşturma

Uzantı, özel etiketler, filtreler, işlevler, sağlayıcılar vb. tanımlayabilen yeniden kullanılabilir bir sınıftır.

Latte özelleştirmelerimizi farklı projelerde yeniden kullanmak veya başkalarıyla paylaşmak istediğimizde uzantılar oluştururuz. Her web projesi için proje şablonlarında kullanmak istediğiniz tüm özel etiketleri ve filtreleri içerecek bir uzantı oluşturmak da yararlıdır.

Uzatma Sınıfı

Uzantı, Latte\Extension adresinden miras alınan bir sınıftır. addExtension() kullanılarak (veya yapılandırma dosyası aracılığıyla) Latte'ye kaydedilir:

$latte = new Latte\Engine;
$latte->addExtension(new MyLatteExtension);

Birden fazla uzantı kaydederseniz ve bunlar aynı adlı etiketleri, filtreleri veya işlevleri tanımlarsa, son eklenen uzantı kazanır. Bu aynı zamanda uzantılarınızın yerel etiketleri/filtreleri/işlevleri geçersiz kılabileceği anlamına gelir.

Bir sınıfta değişiklik yaptığınızda ve otomatik yenileme kapatılmadığında, Latte şablonlarınızı otomatik olarak yeniden derleyecektir.

Bir sınıf aşağıdaki yöntemlerden herhangi birini uygulayabilir:

abstract class Extension
{
	/**
	 * Initializes before template is compiler.
	 */
	public function beforeCompile(Engine $engine): void;

	/**
	 * Returns a list of parsers for Latte tags.
	 * @return array<string, callable>
	 */
	public function getTags(): array;

	/**
	 * Returns a list of compiler passes.
	 * @return array<string, callable>
	 */
	public function getPasses(): array;

	/**
	 * Returns a list of |filters.
	 * @return array<string, callable>
	 */
	public function getFilters(): array;

	/**
	 * Returns a list of functions used in templates.
	 * @return array<string, callable>
	 */
	public function getFunctions(): array;

	/**
	 * Returns a list of providers.
	 * @return array<mixed>
	 */
	public function getProviders(): array;

	/**
	 * Returns a value to distinguish multiple versions of the template.
	 */
	public function getCacheKey(Engine $engine): mixed;

	/**
	 * Initializes before template is rendered.
	 */
	public function beforeRender(Template $template): void;
}

Uzantının neye benzediğine dair bir fikir edinmek için yerleşik "CoreExtension:https://github.com/…xtension.php"a bir göz atın.

beforeCompile (Latte\Engine $engine)void

Şablon derlenmeden önce çağrılır. Bu yöntem, örneğin derlemeyle ilgili başlatmalar için kullanılabilir.

getTags(): array

Şablon derlendiğinde çağrılır. Etiket ayrıştırma işlevleri olan etiket adı ⇒ çağrılabilir bir ilişkisel dizi döndürür.

public function getTags(): array
{
	return [
		'foo' => [FooNode::class, 'create'],
		'bar' => [BarNode::class, 'create'],
		'n:baz' => [NBazNode::class, 'create'],
		// ...
	];
}

n:baz etiketi saf bir n:özniteliği temsil eder, yani yalnızca bir öznitelik olarak yazılabilen bir etikettir.

foo ve bar etiketleri söz konusu olduğunda, Latte bunların çift olup olmadığını otomatik olarak tanıyacaktır ve eğer öyleyse, n:inner-foo ve n:tag-foo öneklerine sahip varyantlar da dahil olmak üzere n:attributes kullanılarak otomatik olarak yazılabilirler.

Bu tür n:özniteliklerin yürütülme sırası getTags() tarafından döndürülen dizideki sıralarına göre belirlenir. Bu nedenle, nitelikler HTML etiketinde ters sırada listelenmiş olsa bile n:foo her zaman n:bar adresinden önce yürütülür. <div n:bar="..." n:foo="...">.

Birden fazla uzantıda n:niteliklerinin sırasını belirlemeniz gerekiyorsa, order() yardımcı yöntemini kullanın; burada before xor after parametresi hangi etiketlerin etiketten önce veya sonra sıralanacağını belirler.

public function getTags(): array
{
	return [
		'foo' => self::order([FooNode::class, 'create'], before: 'bar')]
		'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])]
	];
}

getPasses(): array

Şablon derlendiğinde çağrılır. AST'yi dolaşan ve değiştiren sözde derleyici geçişlerini temsil eden işlevler olan name pass ⇒ callable ilişkisel bir dizi döndürür.

Yine order() yardımcı yöntemi kullanılabilir. before veya after parametrelerinin değeri before/after all anlamında * olabilir.

public function getPasses(): array
{
	return [
		'optimize' => [Passes::class, 'optimizePass'],
		'sandbox' => self::order([$this, 'sandboxPass'], before: '*'),
		// ...
	];
}

beforeRender (Latte\Engine $engine)void

Her şablon render işleminden önce çağrılır. Yöntem, örneğin oluşturma sırasında kullanılan değişkenleri başlatmak için kullanılabilir.

getFilters(): array

Şablon işlenmeden önce çağrılır. Filtreleri ilişkisel bir dizi olarak döndürür filter name ⇒ callable.

public function getFilters(): array
{
	return [
		'batch' => [$this, 'batchFilter'],
		'trim' => [$this, 'trimFilter'],
		// ...
	];
}

getFunctions(): array

Şablon işlenmeden önce çağrılır. İşlevleri bir ilişkisel dizi olarak döndürür işlev adı ⇒ çağrılabilir.

public function getFunctions(): array
{
	return [
		'clamp' => [$this, 'clampFunction'],
		'divisibleBy' => [$this, 'divisibleByFunction'],
		// ...
	];
}

getProviders(): array

Şablon işlenmeden önce çağrılır. Genellikle çalışma zamanında etiketleri kullanan nesneler olan sağlayıcılardan oluşan bir dizi döndürür. Bunlara $this->global->... üzerinden erişilir.

public function getProviders(): array
{
	return [
		'myFoo' => $this->foo,
		'myBar' => $this->bar,
		// ...
	];
}

getCacheKey (Latte\Engine $engine)mixed

Şablon oluşturulmadan önce çağrılır. Dönüş değeri, hash'i derlenen şablon dosyasının adında bulunan anahtarın bir parçası haline gelir. Böylece, farklı geri dönüş değerleri için Latte farklı önbellek dosyaları oluşturacaktır.

Latte Nasıl Çalışır?

Özel etiketlerin veya derleyici geçişlerinin nasıl tanımlanacağını anlamak için, Latte'nin kaputun altında nasıl çalıştığını anlamak önemlidir.

Latte'de şablon derleme basitçe şu şekilde çalışır:

  • İlk olarak, lexer şablon kaynak kodunu daha kolay işlenebilmesi için küçük parçalara (token) ayırır
  • Daha sonra, ayrıştırıcı belirteç akışını anlamlı bir düğüm ağacına (Soyut Sözdizimi Ağacı, AST) dönüştürür
  • Son olarak, derleyici AST'den şablonu işleyen ve önbelleğe alan bir PHP sınıfı oluşturur.

Aslında, derleme biraz daha karmaşıktır. Latte iki sözlükleyici ve ayrıştırıcıya sahiptir: biri HTML şablonu için diğeri de etiketlerin içindeki PHP benzeri kod için. Ayrıca, ayrıştırma tokenizasyondan sonra çalışmaz, ancak lexer ve parser iki “thread” içinde paralel olarak çalışır ve koordine olur. Bu roket bilimi :-)

Ayrıca, tüm etiketlerin kendi ayrıştırma rutinleri vardır. Ayrıştırıcı bir etiketle karşılaştığında, ayrıştırma işlevini çağırır ( Extension::getTags() döndürür). Görevleri, etiket argümanlarını ve eşleştirilmiş etiketler söz konusu olduğunda iç içeriği ayrıştırmaktır. AST'nin bir parçası haline gelen bir node döndürür. Ayrıntılar için Etiket ayrıştırma işlevine bakın.

Ayrıştırıcı işini bitirdiğinde, şablonu temsil eden eksiksiz bir AST'ye sahip oluruz. Kök düğüm Latte\Compiler\Nodes\TemplateNode'dur. Ağacın içindeki tek tek düğümler yalnızca etiketleri değil, aynı zamanda HTML öğelerini, bunların niteliklerini, etiketlerin içinde kullanılan ifadeleri vb. temsil eder.

Bundan sonra, AST'yi değiştiren fonksiyonlar ( Extension::getPasses() tarafından döndürülen) olan Derleyici geçişleri devreye girer.

Şablon içeriğinin yüklenmesinden ayrıştırılmasına ve sonuç dosyasının oluşturulmasına kadar tüm süreç, deneyebileceğiniz ve ara sonuçları dökebileceğiniz bu kodla sıralanabilir:

$latte = new Latte\Engine;
$source = $latte->getLoader()->getContent($file);
$ast = $latte->parse($source);
$latte->applyPasses($ast);
$code = $latte->generate($ast, $file);

AST örneği

AST hakkında daha iyi bir fikir edinmek için bir örnek ekliyoruz. Bu kaynak şablonudur:

{foreach $category->getItems() as $item}
	<li>{$item->name|upper}</li>
	{else}
	no items found
{/foreach}

Bu da 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('no items found')
            )
        )
   )
)

Özel Etiketler

Yeni bir etiket tanımlamak için üç adım gereklidir:

Etiket Ayrıştırma İşlevi

Etiketlerin ayrıştırılması kendi ayrıştırma fonksiyonu ( Extension::getTags() tarafından döndürülen) tarafından gerçekleştirilir. Görevi, etiket içindeki tüm argümanları ayrıştırmak ve kontrol etmektir (bunu yapmak için TagParser kullanır). Ayrıca, etiket bir çift ise, TemplateParser'dan iç içeriği ayrıştırmasını ve döndürmesini isteyecektir. Fonksiyon, genellikle Latte\Compiler\Nodes\StatementNode'un bir çocuğu olan bir düğüm oluşturur ve döndürür ve bu AST'nin bir parçası olur.

Şimdi yapacağımız gibi her düğüm için bir sınıf oluşturacağız ve ayrıştırma işlevini statik bir fabrika olarak zarif bir şekilde içine yerleştireceğiz. Örnek olarak, tanıdık {foreach} etiketini oluşturmayı deneyelim:

use Latte\Compiler\Nodes\StatementNode;

class ForeachNode extends StatementNode
{
	// a parsing function that just creates a node for now
	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = $tag->node = new self;
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		// kod daha sonra eklenecektir
	}

	public function &getIterator(): \Generator
	{
		// kod daha sonra eklenecektir
	}
}

Ayrıştırma işlevi create(), etiketle ilgili temel bilgileri (klasik bir etiket mi yoksa n:attribute mu olduğu, hangi satırda olduğu, vb) taşıyan ve esas olarak $tag->parser içindeki Latte\Compiler\TagParser adresine erişen bir Latte\Compiler\Tag nesnesi aktarır.

Etiketin argümanlara sahip olması gerekiyorsa, $tag->expectArguments() adresini çağırarak bunların varlığını kontrol edin. $tag->parser nesnesinin yöntemleri bunları ayrıştırmak için kullanılabilir:

  • PHP benzeri bir ifade için parseExpression(): ExpressionNode (örn. 10 + 3)
  • parseUnquotedStringOrExpression(): ExpressionNode bir ifade veya tırnaksız dize için
  • parseArguments(): ArrayNode dizinin içeriği (örn. 10, true, foo => bar)
  • parseModifier(): ModifierNode bir değiştirici için (örn. |upper|truncate:10)
  • typehint için parseType(): expressionNode (örn. int|string veya Foo\Bar[])

ve doğrudan jetonlarla çalışan düşük seviyeli bir Latte\Compiler\TokenStream:

  • $tag->parser->stream->consume(...): Token
  • $tag->parser->stream->tryConsume(...): ?Token

Latte, PHP sözdizimini küçük yollarla genişletir, örneğin değiştiriciler, kısaltılmış üçlü operatörler ekleyerek veya basit alfanümerik dizelerin tırnak işaretleri olmadan yazılmasına izin vererek. Bu nedenle PHP yerine PHP benzeri terimini kullanıyoruz. Bu nedenle, örneğin parseExpression() yöntemi foo adresini 'foo' olarak ayrıştırır. Ayrıca, unquoted-string tırnak içine alınması gerekmeyen, ancak aynı zamanda alfanümerik olması da gerekmeyen bir dizginin özel bir durumudur. Örneğin, {include ../file.latte} etiketindeki bir dosyanın yoludur. Bunu ayrıştırmak için parseUnquotedStringOrExpression() yöntemi kullanılır.

Latte'nin bir parçası olan düğüm sınıflarını incelemek, ayrıştırma sürecinin tüm incelikli ayrıntılarını öğrenmenin en iyi yoludur.

{foreach} etiketine geri dönelim. İçinde, aşağıdaki gibi ayrıştırdığımız expression + 'as' + second expression biçiminde argümanlar bekliyoruz:

use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;
use Latte\Compiler\Nodes\AreaNode;

class ForeachNode extends StatementNode
{
	public ExpressionNode $expression;
	public ExpressionNode $value;

	public static function create(Latte\Compiler\Tag $tag): self
	{
		$tag->expectArguments();
		$node = $tag->node = new self;
		$node->expression = $tag->parser->parseExpression();
		$tag->parser->stream->consume('as');
		$node->value = $parser->parseExpression();
		return $node;
	}
}

$expression ve $value değişkenlerine yazdığımız ifadeler alt düğümleri temsil etmektedir.

Alt düğümleri olan değişkenleri public olarak tanımlayın, böylece gerekirse sonraki işlem adımlarında değiştirilebilirler. Ayrıca, bunları çaprazlama için ulaşılabilir kılmak da gereklidir.

Bizimki gibi eşleştirilmiş etiketler için, yöntem TemplateParser'ın etiketin iç içeriğini ayrıştırmasına da izin vermelidir. Bu işlem, [iç içerik, son etiket] çiftini döndüren yield tarafından gerçekleştirilir. İç içeriği $node->content değişkeninde saklıyoruz.

public AreaNode $content;

public static function create(Latte\Compiler\Tag $tag): \Generator
{
	// ...
	[$node->content, $endTag] = yield;
	return $node;
}

yield anahtar sözcüğü create() yönteminin sonlanmasına neden olarak kontrolü TemplateParser'a geri döndürür ve TemplateParser end etiketine ulaşana kadar içeriği ayrıştırmaya devam eder. Daha sonra kontrolü, kaldığı yerden devam eden create() yöntemine geri aktarır. yield , yöntemi kullanıldığında otomatik olarak Generator döndürülür.

Ayrıca yield adresine, son etiketten önce gelirlerse ayrıştırmayı durdurmak istediğiniz bir dizi etiket adı da iletebilirsiniz. Bu bize şu yöntemi uygulamamıza yardımcı olur {foreach}...{else}...{/foreach} yapı. Eğer {else} oluşursa, ondan sonraki içeriği $node->elseContent olarak ayrıştırırız:

public AreaNode $content;
public ?AreaNode $elseContent = null;

public static function create(Latte\Compiler\Tag $tag): \Generator
{
	// ...
	[$node->content, $nextTag] = yield ['else'];
	if ($nextTag?->name === 'else') {
		[$node->elseContent] = yield;
	}

	return $node;
}

Dönen düğüm etiket ayrıştırmasını tamamlar.

PHP Kodu Oluşturma

Her düğüm print() yöntemini uygulamalıdır. Şablonun verilen bölümünü işleyen PHP kodunu döndürür (çalışma zamanı kodu). Parametre olarak bir Latte\Compiler\PrintContext nesnesi aktarılır; bu nesne, elde edilen kodun montajını basitleştiren kullanışlı bir format() yöntemine sahiptir.

format(string $mask, ...$args) yöntemi maskede aşağıdaki yer tutucuları kabul eder:

  • %node düğüm yazdırır
  • %dump değeri PHP'ye aktarır
  • %raw metni herhangi bir dönüştürme yapmadan doğrudan ekler
  • %args fonksiyon çağrısına argüman olarak ArrayNode yazdırır
  • %line satır numarasıyla birlikte bir yorum yazdırır
  • %escape(...) içerikten kaçar
  • %modify(...) bir değiştirici uygular
  • %modifyContent(...) bloklara bir değiştirici uygular

print() fonksiyonumuz aşağıdaki gibi görünebilir (basitlik için else dalını ihmal ediyoruz):

public function print(Latte\Compiler\PrintContext $context): string
{
	return $context->format(
		<<<'XX'
			foreach (%node as %node) %line {
				%node
			}

			XX,
		$this->expression,
		$this->value,
		$this->position,
		$this->content,
	);
}

$this->position değişkeni Latte\Compiler\Node sınıfı tarafından zaten tanımlanmıştır ve ayrıştırıcı tarafından ayarlanır. Etiketin kaynak koddaki konumunu satır ve sütun numarası şeklinde gösteren bir Latte\Compiler\Position nesnesi içerir.

Çalışma zamanı kodu yardımcı değişkenler kullanabilir. Şablonun kendisi tarafından kullanılan değişkenlerle çakışmayı önlemek için, bunların önüne $ʟ__ karakterlerinin eklenmesi gelenekseldir.

Ayrıca, çalışma zamanında Extension::getProviders() yöntemi kullanılarak şablona sağlayıcılar şeklinde aktarılan rastgele değerler de kullanabilir. Bunlara $this->global->... adresini kullanarak erişir.

AST Çaprazlama

AST ağacını derinlemesine dolaşmak için getIterator() yöntemini uygulamak gerekir. Bu, alt düğümlere erişim sağlayacaktır:

public function &getIterator(): \Generator
{
	yield $this->expression;
	yield $this->value;
	yield $this->content;
	if ($this->elseContent) {
		yield $this->elseContent;
	}
}

getIterator() adresinin bir referans döndürdüğüne dikkat edin. Bu, düğüm ziyaretçilerinin tek tek düğümleri diğer düğümlerle değiştirmesini sağlar.

Bir düğümün alt düğümleri varsa, bu yöntemi uygulamak ve tüm alt düğümleri kullanılabilir hale getirmek gerekir. Aksi takdirde bir güvenlik açığı oluşabilir. Örneğin, sandbox modu alt düğümleri kontrol edemeyecek ve izin verilmeyen yapıların içlerinde çağrılmamasını sağlayamayacaktır.

Alt düğümleri olmasa bile yield anahtar sözcüğünün yöntem gövdesinde bulunması gerektiğinden, aşağıdaki gibi yazın:

public function &getIterator(): \Generator
{
	if (false) {
		yield;
	}
}

AuxiliaryNode

Latte için yeni bir etiket oluşturuyorsanız, bunun için AST ağacında temsil edecek özel bir düğüm sınıfı oluşturmanız tavsiye edilir (yukarıdaki örnekte ForeachNode sınıfına bakın). Bazı durumlarda, print() yönteminin gövdesini ve getIterator() yöntemi tarafından erişilebilir kılınan düğümlerin listesini yapıcı parametreleri olarak geçirmenize olanak tanıyan önemsiz yardımcı düğüm sınıfı AuxiliaryNode 'u yararlı bulabilirsiniz:

// Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode
// or Latte\Compiler\Nodes\AuxiliaryNode

$node = new AuxiliaryNode(
	// body of the print() method:
	fn(PrintContext $context, $argNode) => $context->format('myFunc(%node)', $argNode),
	// nodes accessed via getIterator() and also passed into the print() method:
	[$argNode],
);

Derleyici Geçişleri

Derleyici Geçişleri, AST'leri değiştiren veya içlerindeki bilgileri toplayan işlevlerdir. Extension::getPasses() yöntemi tarafından döndürülürler.

Düğüm Çaprazlayıcı

AST ile çalışmanın en yaygın yolu bir Latte\Compiler\NodeTraverser kullanmaktır:

use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;

$ast = (new NodeTraverser)->traverse(
	$ast,
	enter: fn(Node $node) => ...,
	leave: fn(Node $node) => ...,
);

Enter* fonksiyonu (yani ziyaretçi) bir düğümle ilk karşılaşıldığında, alt düğümleri işlenmeden önce çağrılır. Tüm alt düğümler ziyaret edildikten sonra leave fonksiyonu çağrılır. Yaygın bir model, enter işlevinin bazı bilgileri toplamak için kullanılması ve ardından leave işlevinin buna dayalı değişiklikler yapmasıdır. leave* çağrıldığında, düğüm içindeki tüm kodlar zaten ziyaret edilmiş ve gerekli bilgiler toplanmış olacaktır.

AST nasıl değiştirilir? En kolay yol basitçe düğümlerin özelliklerini değiştirmektir. İkinci yol ise yeni bir düğüm döndürerek düğümü tamamen değiştirmektir. Örnek: Aşağıdaki kod AST'deki tüm tamsayıları string olarak değiştirecektir (örneğin 42, '42' olarak değiştirilecektir).

use Latte\Compiler\Nodes\Php;

$ast = (new NodeTraverser)->traverse(
	$ast,
	leave: function (Node $node) {
		if ($node instanceof Php\Scalar\IntegerNode) {
            return new Php\Scalar\StringNode((string) $node->value);
        }
	},
);

Bir AST kolayca binlerce düğüm içerebilir ve hepsinin üzerinden geçmek yavaş olabilir. Bazı durumlarda, tam bir çaprazlamadan kaçınmak mümkündür.

Bir ağaçta tüm Html\ElementNode adreslerini arıyorsanız, Php\ExpressionNode adresini gördükten sonra tüm alt düğümlerini de kontrol etmenin bir anlamı olmadığını bilirsiniz, çünkü HTML ifadelerin içinde olamaz. Bu durumda, traverser'a sınıf düğümüne geri dönmemesi talimatını verebilirsiniz:

$ast = (new NodeTraverser)->traverse(
	$ast,
	enter: function (Node $node) {
		if ($node instanceof Php\ExpressionNode) {
			return NodeTraverser::DontTraverseChildren;
        }
        // ...
	},
);

Yalnızca belirli bir düğümü arıyorsanız, bulduktan sonra geçişi tamamen iptal etmek de mümkündür.

$ast = (new NodeTraverser)->traverse(
	$ast,
	enter: function (Node $node) {
		if ($node instanceof Nodes\ParametersNode) {
			return NodeTraverser::StopTraversal;
        }
        // ...
	},
);

Düğüm Yardımcıları

Latte\Compiler\NodeHelpers sınıfı, belirli bir geri çağrıyı vb. karşılayan AST düğümlerini bulabilen bazı yöntemler sağlar. Birkaç örnek gösterilmiştir:

Latte\Compiler\NodeHelpers kullanın;

// tüm HTML öğesi düğümlerini bulur
$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode);

// ilk metin düğümünü bulur
$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode);

// PHP değer düğümünü gerçek değere dönüştürür
$value = NodeHelpers::toValue($node);

// statik metinsel düğümü dizeye dönüştürür
$text = NodeHelpers::toText($node);
versiyon: 3.0