Şablon kalıtımı ve yeniden kullanılabilirlik

Şablonların yeniden kullanılabilirlik ve kalıtım mekanizmaları verimliliğinizi artırmak için buradadır; çünkü her şablon yalnızca kendine özgü içeriği barındırır, yinelenen öğeler ve yapılar ise yeniden kullanılır. Üç kavram tanıtıyoruz: Layout Inheritance, Horizontal Reuse ve Unit Inheritance.

Latte şablon kalıtımı kavramı, PHP sınıf kalıtımına benzer. Başka alt şablonların kalıtım alabileceği ve bölümlerini ezebileceği bir üst şablon tanımlarsınız. Öğeler ortak bir yapıyı paylaştığında harika çalışır. Karmaşık mı geliyor? Merak etmeyin, çok kolay.

Layout kalıtımı {layout}

Layout şablonu kalıtımına bir örnekle bakalım. Bu, bir HTML belgesinin iskeletini tanımlayan üst şablondur; ona layout.latte diyelim:

<!doctype html>
<html lang="en">
<head>
	<title>{block title}{/block}</title>
	<link rel="stylesheet" href="style.css">
</head>
<body>
	<div id="content">
		{block content}{/block}
	</div>
	<div id="footer">
		{block footer}&copy; Copyright 2008{/block}
	</div>
</body>
</html>

{block} etiketleri, alt şablonların doldurabileceği üç blok tanımlar. Blok etiketinin tek yaptığı, şablon motoruna bir alt şablonun aynı adlı kendi bloğunu tanımlayarak bu bölümleri ezebileceğini söylemektir.

Bir alt şablon şöyle görünebilir:

{layout 'layout.latte'}

{block title}My amazing blog{/block}

{block content}
	<p>Welcome to my awesome homepage.</p>
{/block}

Burada {layout} etiketi anahtardır. Latte'ye bu şablonun başka bir şablonu “genişlettiğini” söyler. Latte bu şablonu render ederken önce üst şablonu bulur; bu durumda layout.latte'yi.

Bu noktada Latte, layout.latte içindeki üç blok etiketini fark eder ve bu blokları alt şablonun içeriğiyle değiştirir. Alt şablon footer bloğunu tanımlamadığından, onun yerine üst şablondaki içerik kullanılır. Üst şablondaki {block} etiketinin içindeki içerik her zaman yedek olarak kullanılır.

Çıktı şöyle görünebilir:

<!doctype html>
<html lang="en">
<head>
	<title>My amazing blog</title>
	<link rel="stylesheet" href="style.css">
</head>
<body>
	<div id="content">
		<p>Welcome to my awesome homepage.</p>
	</div>
	<div id="footer">
		&copy; Copyright 2008
	</div>
</body>
</html>

Alt şablonda bloklar genellikle en üst düzeyde ya da başka bir bloğun içinde yer alır, örneğin:

{block content}
	<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}

Ayrıca bir blok, çevresindeki {if} koşulunun doğru mu yanlış mı değerlendirildiğinden bağımsız olarak her zaman oluşturulur. Yani öyle görünmese de, bu şablon bloğu tanımlar.

{if false}
	{block head}
		<meta name="robots" content="noindex, follow">
	{/block}
{/if}

Bloğun içindeki çıktının koşullu gösterilmesini istiyorsanız, onun yerine şunu kullanın:

{block head}
	{if $condition}
		<meta name="robots" content="noindex, follow">
	{/if}
{/block}

Alt şablonun başlığındaki kod (yani ilk bloktan veya herhangi bir çıktıdan önceki kod) layout şablonu render edilmeden önce çalıştırılır, bu yüzden onu {var $foo = bar} gibi değişkenler tanımlamak ve veriyi kalıtım zinciri boyunca yaymak için kullanabilirsiniz. {layout} içeren bir şablonda blokların arasına veya sonrasına konan kod hiç çalıştırılmaz:

{layout 'layout.latte'}
{var $robots = noindex}

...

Değişkenleri, geçerli şablonda oluşturmadan yalnızca layout'a aktarmak isterseniz, onları doğrudan {layout} (veya {extends}) etiketinde virgülden sonra listeleyin:

{layout 'layout.latte', robots: noindex}

$robots değişkeni layout'ta ve onun bloklarında erişilebilir olur, ama geçerli şablonun bloklarında olmaz. Açıkça aktarılan bir değişken, aynı adlı bir şablon parametresinden de önceliklidir.

Çok düzeyli kalıtım

İhtiyaç duyduğunuz kadar kalıtım düzeyi kullanabilirsiniz. Layout kalıtımını kullanmanın yaygın bir yolu şu üç düzeyli yaklaşımdır:

  1. Sitenizin ana görünümünü ve havasını tutan bir layout.latte şablonu oluşturun.
  2. Sitenizin her bölümü için bir layout-BOLUMADI.latte şablonu oluşturun. Örneğin layout-news.latte, layout-blog.latte vb. Tüm bu şablonlar layout.latte'yi genişletir ve her bölüme özgü stilleri ve tasarımı içerir.
  3. Bir haber makalesi veya blog gönderisi gibi her sayfa tipi için ayrı şablonlar oluşturun. Bu şablonlar uygun bölüm şablonunu genişletir.

Dinamik layout kalıtımı

Üst şablonun adı olarak bir değişken veya herhangi bir PHP ifadesi kullanabilirsiniz, böylece kalıtım dinamik davranabilir:

{layout $standalone ? 'minimum.latte' : 'layout.latte'}

Layout şablonunu otomatik seçmek için Latte API'sini de kullanabilirsiniz.

İpuçları

İşte layout kalıtımıyla çalışmak için birkaç ipucu:

  • Bir şablonda {layout} kullanıyorsanız, şablonun başlığında, yani herhangi bir çıktıdan önce olmalıdır. Yalnızca çıktı üretmeyen etiketler ({var}, {templateType}, {import} veya yorumlar gibi) ondan önce gelebilir.
  • Layout otomatik bulunabilir (presenter'larda olduğu gibi). Bu durumda, şablonun layout'u olmaması gerekiyorsa bunu {layout none} etiketiyle belirtir. Tersine, {layout auto} (veya {extends auto}) otomatik layout aramasını geri getirir.
  • {layout} etiketinin bir takma adı vardır: {extends}.
  • Layout dosyasının adı loader'a bağlıdır.
  • İstediğiniz kadar bloğunuz olabilir. Alt şablonların tüm üst blokları tanımlamak zorunda olmadığını unutmayın; böylece birkaç blokta makul varsayılanlar doldurup sonra yalnızca ihtiyacınız olanları tanımlayabilirsiniz.

Bloklar {block}

Ayrıca bkz. anonim {block}

Bir blok, bir şablonun belirli bir bölümünün nasıl render edileceğini değiştirmenin bir yolunu sunar, ama çevresindeki mantığa hiçbir şekilde karışmaz. Bir bloğun nasıl çalıştığını, daha da önemlisi nasıl çalışmadığını şu örnekle gösterelim:

{foreach $posts as $post}
{block post}
	<h1>{$post->title}</h1>
	<p>{$post->body}</p>
{/block}
{/foreach}

Bu şablonu render ederseniz, sonuç {block} etiketleriyle de onlarsız da tam olarak aynı olur. Bloklar dış kapsamlardaki değişkenlere erişebilir. Yalnızca bir alt şablon tarafından ezilme olanağı sağlarlar:

{layout 'parent.latte'}

{block post}
	<article>
		<header>{$post->title}</header>
		<section>{$post->text}</section>
	</article>
{/block}

Şimdi alt şablon render edilirken döngü, parent.latte'de tanımlanan blok yerine child.latte alt şablonunda tanımlanan bloğu kullanır; çalıştırılan şablon o zaman şuna denktir:

{foreach $posts as $post}
	<article>
		<header>{$post->title}</header>
		<section>{$post->text}</section>
	</article>
{/foreach}

Ancak adlandırılmış bir bloğun içinde yeni bir değişken oluşturursak ya da var olan birinin değerini değiştirirsek, değişiklik yalnızca bloğun içinde görünür olur:

{var $foo = 'foo'}
{block post}
	{do $foo = 'new value'}
	{var $bar = 'bar'}
{/block}

foo: {$foo}                  // yazdırır: foo
bar: {$bar ?? 'not defined'} // yazdırır: not defined

Bir bloğun içeriği filtrelerle değiştirilebilir. Şu örnek tüm HTML'i kaldırır ve büyük harfe çevirir:

<title>{block title|stripHtml|capitalize}...{/block}</title>

Etiket bir n:nitelik olarak da yazılabilir:

<article n:block=post>
	...
</article>

Yerel bloklar

Her blok, aynı adlı üst bloğun içeriğini ezer; yerel bloklar dışında. Onlar sınıflardaki private metotlara benzer. Blok adlarının rastlantı eseri çakışması yüzünden başka bir şablon tarafından ezilecekleri endişesi olmadan bir şablon oluşturabilirsiniz.

{block local helper}
	...
{/block}

Blokları yazdırma {include}

Ayrıca bkz. {include file}

Bir bloğu belirli bir yerde yazdırmak için {include blokadi} etiketini kullanın:

<title>{block title}{/block}</title>

<h1>{include title}</h1>

Başka bir şablondan da blok yazdırabilirsiniz:

{include footer from 'main.latte'}

Render edilen blok, aynı dosyada tanımlanmadıkça etkin bağlamın değişkenlerine erişemez. Ancak genel değişkenlere erişebilir.

Bloğa değişkenleri şöyle aktarabilirsiniz:

{include footer, foo: bar, id: 123}

Blok adı bir değişken veya herhangi bir PHP ifadesi olabilir. Bu durumda, Latte'nin derleme zamanında bunun bir blok olduğunu, adı yine bir değişkende olabilecek eklenen bir şablon olmadığını bilmesi için değişkenin önüne block anahtar sözcüğünü ekleyin:

{var $name = footer}
{include block $name}

Bir blok kendi içinde de render edilebilir; bu, örneğin bir ağaç yapısı render edilirken yararlıdır:

{define menu, $items}
<ul>
	{foreach $items as $item}
		<li>
		{if is_array($item)}
			{include menu, $item}
		{else}
			{$item}
		{/if}
		</li>
	{/foreach}
</ul>
{/define}

{include menu, ...} yerine {include this, ...} da yazabiliriz; burada this geçerli bloğu ifade eder.

Render edilen blok içeriği filtrelerle değiştirilebilir. Şu örnek tüm HTML'i kaldırır ve büyük harfe çevirir:

<title>{include heading|stripHtml|capitalize}</title>

Üst blok

Üst şablondaki bloğun içeriğini yazdırmanız gerekiyorsa {include parent} kullanın. Üst bloğun içeriğini tamamen ezmek yerine tamamlamak istiyorsanız yararlıdır.

{block footer}
	{include parent}
	<a href="https://github.com/nette">GitHub</a>
	<a href="https://twitter.com/nettefw">Twitter</a>
{/block}

Tanımlar {define}

Latte'de blokların yanı sıra “tanımlar” da vardır. Yaygın programlama dillerinde fonksiyonlara benzetilebilirler. Yinelemeden kaçınmak için şablon parçalarını yeniden kullanmakta işe yararlar.

Latte işleri basit tutmaya çalışır, bu yüzden tanımlar temelde bloklarla aynıdır ve bloklar hakkında söylenen her şey tanımlar için de geçerlidir. Bloklardan şu yönleriyle ayrılırlar:

  1. {define} etiketlerinin içine alınırlar
  2. yalnızca {include} ile eklendiklerinde render edilirler
  3. PHP'deki fonksiyonlara benzer şekilde onlara parametre tanımlayabilirsiniz
{block foo}<p>Hello</p>{/block}
{* yazdırır: <p>Hello</p> *}

{define bar}<p>World</p>{/define}
{* hiçbir şey yazdırmaz *}

{include bar}
{* yazdırır: <p>World</p> *}

HTML formlarının nasıl çizileceğine dair tanımlar koleksiyonu içeren bir yardımcı şablonunuz olduğunu düşünün.

{define input, $name, $value, $type = 'text'}
	<input type={$type} name={$name} value={$value}>
{/define}

{define textarea, $name, $value}
	<textarea name={$name}>{$value}</textarea>
{/define}

Argümanlar, bir varsayılan değer belirtilmedikçe her zaman isteğe bağlıdır ve varsayılan değerleri null'dır (burada 'text', $type için varsayılan değerdir). Parametre tipleri de bildirilebilir: {define input, string $name, ...}.

Tanımları içeren şablon {import} ile yüklenir. Tanımların kendisi bloklarla aynı şekilde render edilir:

<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>

Bloklar gibi tanımlar da etkin bağlamın değişkenlerine erişemez, yalnızca genel değişkenlere erişebilir. İstisna, bildirilmiş parametresi olmayan, statik bir adla başvurulan ve tanımlandığı dosyada eklenen bir tanımdır: böyle bir tanım, eklendiği yerin bağlam değişkenlerine erişebilir.

Dinamik blok adları

Latte, blokları tanımlamada büyük esneklik sağlar, çünkü blok adı herhangi bir PHP ifadesi olabilir. Şu örnek hi-Peter, hi-John ve hi-Mary adlı üç blok tanımlar:

{foreach [Peter, John, Mary] as $name}
	{block "hi-$name"}Hi, I am {$name}.{/block}
{/foreach}

Alt şablonda ise örneğin yalnızca bir bloğu yeniden tanımlayabiliriz:

{block hi-John}Hello. I am {$name}.{/block}

Böylece çıktı şöyle görünecektir:

Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.

Blok varlığını denetleme {ifset}

Ayrıca bkz. {ifset $var}

Geçerli bağlamda bir bloğun (veya birden fazla bloğun) var olup olmadığını denetlemek için {ifset blokadi} testini kullanın:

{ifset footer}
	...
{/ifset}

{ifset footer, header, main}
	...
{/ifset}

Blok adı bir değişken veya herhangi bir PHP ifadesi olabilir. Bu durumda, bunun değişkenlerin varlığını denetlemek olmadığını netleştirmek için değişkenin önüne block anahtar sözcüğünü ekleyin:

{ifset block $name}
	...
{/ifset}

Blokların varlığını hasBlock() fonksiyonu da denetler:

{if hasBlock(header) || hasBlock(footer)}
	...
{/if}

İpuçları

Bloklarla çalışmak için birkaç ipucu:

  • En üst düzeydeki son bloğun kapanış etiketine gerek yoktur (blok, belgenin sonuyla biter). Bu, tek bir birincil blok içeren alt şablonları yazmayı kolaylaştırır.
  • Daha iyi okunabilirlik için, isteğe bağlı olarak blok adını {/block} etiketinde de verebilirsiniz, örneğin {/block footer}. Ancak ad, blok adıyla eşleşmelidir. Daha büyük şablonlarda bu teknik, hangi blok etiketlerinin kapandığını görmenize yardım eder.
  • Aynı şablonda aynı adlı birden fazla blok etiketini doğrudan tanımlayamazsınız. Ama bu, Dinamik blok adları ile sağlanabilir.
  • Blokları tanımlamak için n:nitelikleri kullanabilirsiniz, örneğin <h1 n:block=title>Welcome to my awesome homepage</h1>
  • Bloklar, yalnızca çıktıya filtre uygulamak için adsız da kullanılabilir: {block|strip} hello {/block}

Yatay yeniden kullanım {import}

Yatay yeniden kullanım, Latte'de yeniden kullanım ve kalıtımın üçüncü mekanizmasıdır. Başka şablonlardan blok yüklemeyi sağlar. PHP'de yardımcı fonksiyonlar içeren bir dosya oluşturup sonra onu require ile yüklemeye benzer.

Şablon layout kalıtımı Latte'nin en güçlü özelliklerinden biri olsa da, basit kalıtımla sınırlıdır: bir şablon yalnızca başka bir şablonu genişletebilir. Yatay yeniden kullanım, çoklu kalıtımı elde etmenin bir yoludur.

Blok tanımları içeren bir dosyamız olsun:

{block sidebar}...{/block}

{block menu}...{/block}

{import} komutuyla, blocks.latte içinde tanımlanan tüm blokları ve Definitions'ı başka bir şablona aktarırız:

{import 'blocks.latte'}

{* sidebar ve menu blokları artık kullanılabilir *}

Blokları üst şablonda içe aktarırsanız (yani {import}'u layout.latte'de kullanırsanız), bloklar tüm alt şablonlarda da erişilebilir olur; bu çok pratiktir.

İçe aktarılması amaçlanan şablon (örneğin blocks.latte), başka bir şablonu genişletmemelidir, yani {layout} kullanmamalıdır. Ancak başka şablonları içe aktarabilir.

{import} etiketi, {layout}'tan sonraki ilk şablon etiketi olmalıdır. Şablon adı herhangi bir PHP ifadesi olabilir:

{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}

Bir şablonda istediğiniz kadar {import} deyimi kullanabilirsiniz. İçe aktarılan iki şablon aynı bloğu tanımlarsa, ilki kazanır. Ancak ana şablonun en yüksek önceliği vardır ve içe aktarılan herhangi bir bloğu ezebilir.

{import} etiketi, içe aktarılan şablona argüman da aktarabilir, örneğin {import 'blocks.latte', foo: 1}. Bu argümanlar sonra içe aktarılan bloklarda ve tanımlarda değişken olarak erişilebilir olur.

Ezilen blokların içeriği, bloğu bir Üst blok gibi ekleyerek korunabilir:

{layout 'layout.latte'}

{import 'blocks.latte'}

{block sidebar}
	{include parent}
{/block}

{block title}...{/block}
{block content}...{/block}

Bu örnekte {include parent}, blocks.latte şablonundaki sidebar bloğunu çağırır.

Birim kalıtımı {embed}

Birim kalıtımı, layout kalıtımı fikrini içerik parçaları düzeyine taşır. Layout kalıtımı, alt şablonların canlandırdığı “belge iskeletleriyle” çalışırken, birim kalıtımı daha küçük içerik birimleri için iskeletler oluşturmanıza ve onları istediğiniz her yerde yeniden kullanmanıza olanak tanır.

Birim kalıtımında anahtar, {embed} etiketidir. {include} ile {layout}'un davranışını birleştirir. Başka bir şablonun ya da bloğun içeriğini gömmenize ve isteğe bağlı olarak, tıpkı {include} gibi, değişken aktarmanıza olanak tanır. Ayrıca, tıpkı {layout} gibi, gömülen şablonun içinde tanımlanan herhangi bir bloğu ezmenizi sağlar.

Örneğin bir akordiyon öğesi kullanalım. collapsible.latte şablonunda saklanan öğe iskeletine bakın:

<section class="collapsible {$modifierClass}">
	<h4 class="collapsible__title">
		{block title}{/block}
	</h4>

	<div class="collapsible__content">
		{block content}{/block}
	</div>
</section>

{block} etiketleri, alt şablonların doldurabileceği iki blok tanımlar. Evet, tıpkı layout kalıtımındaki üst şablonda olduğu gibi. $modifierClass değişkenini de görüyorsunuz.

Öğemizi bir şablonda kullanalım. İşte burada {embed} devreye girer. Tüm bunları yapmamızı sağlayan son derece güçlü bir etikettir: öğenin şablon içeriğini gömmek, ona değişken eklemek ve ona özel HTML içeren bloklar eklemek:

{embed 'collapsible.latte', modifierClass: my-style}
	{block title}
		Hello World
	{/block}

	{block content}
		<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
		elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
	{/block}
{/embed}

Çıktı şöyle görünebilir:

<section class="collapsible my-style">
	<h4 class="collapsible__title">
		Hello World
	</h4>

	<div class="collapsible__content">
		<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
		elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
	</div>
</section>

Embed etiketlerinin içindeki bloklar, embed'in dışındaki bloklardan yalıtılmış ayrı bir katman oluşturur. Bu yüzden dışarıdaki bir blokla aynı ada sahip olabilirler, çakışmazlar ve ondan etkilenmezler. {embed} etiketlerinin içinde include etiketini kullanarak, burada oluşturulan blokları, gömülen şablondaki (yerel olmayan) blokları ve ayrıca ana şablondaki yerel olan blokları ekleyebilirsiniz. Başka dosyalardan blok içe aktarabilirsiniz de:

{block outer}…{/block}
{block local hello}…{/block}

{embed 'collapsible.latte', modifierClass: my-style}
	{import 'blocks.latte'}

	{block inner}…{/block}

	{block title}
		{include inner} {* çalışır, blok embed'in içinde tanımlı *}
		{include hello} {* çalışır, blok bu şablonda yerel *}
		{include content} {* çalışır, blok gömülen şablonda tanımlı *}
		{include aBlockDefinedInImportedTemplate} {* çalışır *}
		{include outer} {* çalışmaz! - blok dış katmanda *}
	{/block}
{/embed}

Gömülen şablonlar etkin bağlamın değişkenlerine erişemez, ama genel değişkenlere erişebilir.

{embed} ile yalnızca şablonları değil, başka blokları da gömebilirsiniz; yani önceki örnek şöyle de yazılabilirdi:

{define collapsible}
<section class="collapsible {$modifierClass}">
	<h4 class="collapsible__title">
		{block title}{/block}
	</h4>
	...
</section>
{/define}


{embed collapsible, modifierClass: my-style}
	{block title}
		Hello World
	{/block}
	...
{/embed}

Ancak ikisi arasında bir fark vardır: bir dosya yerine bir blok gömdüğünüzde, dış katmandaki bloklar embed'in içinde erişilebilir kalır. Yani gömülen bir dosyanın aksine, orada {include outer} çalışırdı.

{embed}'e bir ifade aktarırsak ve bunun bir blok adı mı yoksa bir dosya adı mı olduğu belli değilse, block veya file anahtar sözcüğünü ekleyin:

{embed block $name} ... {/embed}

Kullanım durumları

Latte'de çeşitli kalıtım ve kod yeniden kullanım tipleri vardır. Daha iyi anlaşılırlık için başlıca kavramları özetleyelim:

{include template}

Kullanım durumu: layout.latte içinde header.latte ve footer.latte kullanmak.

header.latte

<nav>
   <div>Home</div>
   <div>About</div>
</nav>

footer.latte

<footer>
   <div>Copyright</div>
</footer>

layout.latte

{include 'header.latte'}

<main>{block main}{/block}</main>

{include 'footer.latte'}

{layout}

Kullanım durumu: homepage.latte ve about.latte içinde layout.latte'yi genişletmek.

layout.latte

{include 'header.latte'}

<main>{block main}{/block}</main>

{include 'footer.latte'}

homepage.latte

{layout 'layout.latte'}

{block main}
	<p>Homepage</p>
{/block}

about.latte

{layout 'layout.latte'}

{block main}
	<p>About page</p>
{/block}

{import}

Kullanım durumu: single.product.latte ve single.service.latte içinde sidebar.latte kullanmak.

sidebar.latte

{block sidebar}<aside>This is sidebar</aside>{/block}

single.product.latte

{layout 'product.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Product page</main>{/block}

single.service.latte

{layout 'service.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Service page</main>{/block}

{define}

Kullanım durumu: değişken alıp bir şey render eden fonksiyonlar.

form.latte

{define form-input, $name, $value, $type = 'text'}
	<input type={$type} name={$name} value={$value}>
{/define}

profile.service.latte

{import 'form.latte'}

<form action="" method="post">
	<div>{include form-input, username}</div>
	<div>{include form-input, password}</div>
	<div>{include form-input, submit, Submit, submit}</div>
</form>

{embed}

Kullanım durumu: product.table.latte ve service.table.latte içine pagination.latte gömmek.

pagination.latte

<div id="pagination">
	<div>{block first}{/block}</div>

	{for $i = $min + 1; $i < $max - 1; $i++}
		<div>{$i}</div>
	{/for}

	<div>{block last}{/block}</div>
</div>

product.table.latte

{embed 'pagination.latte', min: 1, max: $products->count}
	{block first}First Product Page{/block}
	{block last}Last Product Page{/block}
{/embed}

service.table.latte

{embed 'pagination.latte', min: 1, max: $services->count}
	{block first}First Service Page{/block}
	{block last}Last Service Page{/block}
{/embed}
versiyon: 3.x