Twig Örnek İşlemleri

Grav temalarında Twig şablon motorunun kullanıldığından daha önce bahsetmiştim.

AA

Bu yazıda Grav ile ilgili temel bilgiler verdiğim (dosya yapısı, konfigürasyon, sayfa tipleri vb.) yazının da bir uzantısı olarak çeşitli Twig kullanımlarına değineceğim. Bu yazıda yer alan pratikler Grav teması geliştirirken kullanılmak üzere referans görevi görecekler.

Aşağıdaki örneklerin çalışabilmesi için çalışma ortamınızda Twig’in kurulu olması gerekmekte. Daha önce macOS işletim sisteminde PHP kodlarının doğrudan nasıl çalıştırılabileceğine değinmiştim. Linux dağıtımlarında da benzer seçenekler mevcut. Önemli bir hatırlatma; Twig PHP 7.2.0 ve üzeri bir sürüm gereksinimine sahip.

O halde composer aracılığıyla Twig kurulumuna başlayabiliriz.

composer require "twig/twig:^3.0"

Kurulumun tamamlanmasının ardından örnekleri uygulamaya başlayabilirsiniz. Şayet, Grav üzerinden örnekleri uygulayacaksanız Twig kurulumuna ihtiyacınız yok. Grav hali hazırda bu kuruluma sahip. Doğrudan kodları çalıştırmaya başlayabilirsiniz. Temel bazı hatırlatmalarla başlayalım.

Twig komutları belli bir uzantı kısıtlaması olmaksızın, normal birer metin dosyası (html, php, …) içerisinde yer alır. Genelde Grav temaları içerisinde dosyaadi.html.twig şeklinde karşınıza çıkacaklardır. Bir şablon değişkenler ve ifadeler içerir. Bu değişkenler ve ifadeler veri ile birlikte şablon motoru tarafından işlenir ve sonuçları uygulayarak dosyanın son halini görüntülememizi sağlar. Aşağıda, bu süreci özetleyen temel bir örnek yer almakta. Kodları ayrıca TwigFiddle1 aracılığıyla da test edebilirsiniz. Yazım önerileri için Coding Standards2 sayfasına göz atabilirsiniz.

<!DOCTYPE html>
<html>
    <head>
        <title>{{ page.title }}</title>
    </head>
    <body>
        <h1>{{ title }}</h1>
        <p>{{ content }}</p>
        <ul id="navigation">
        {% for item in navigation %}
            <li>{{ loop.index }}<a href="{{ item.href }}" title="{{ item.caption }}">{{ item.caption }}</a></li>
        {% endfor %}
        </ul>
    </body>
</html>

Değişkenler (Variables)

Twig {{...}} ile değişkenleri kullanabilmemizi sağlar. Grav temaları geliştirilirken pek çok ön tanımlı değişkeni temaya ait blueprint.yaml dosyasında tutarız. Bu değişkenlere {{ theme.foo }} şeklinde ulaşabiliriz3. Unutmadan, süslü parantezler değişken ifadesinin bir parçası değildir, yazdırma (print) işlemi için kullanılırlar.

{{ url("theme://assets/img/image.png") }}

Yukarıdaki örnekte url fonksiyonu içerisindeki theme:// aktif temanın yolunu vermektedir. Dolayısıya print sonucunda elimizde image.png görselinin tam yolu (relative path) olacaktır. Aynı işlemi eklentiler için plugin:// ve sayfalar için page:// olarak kullanırız.

Mantıksal işlemler diğer bir ifade edile kontrol yapıları için ise tanımlarımızı {% ... %} ile gerçekleştiririz. Güncel Twig etiketleri ve tüm mantıksal işlemler için doc/3.x4 sayfasını kullanabilirsiniz.

{% do assets.addCss('plugin://assets/css/spectre.css') %}
{% do assets.addJs('page://01.blog/assets/example.js', {loading: 'async', id: 'custom-css'}) %}

assets.addCss ve assets.addJs kullanımına ayrıca değineceğim. Şimdilik, kendi değişkenlerimizi kod içerisinde nasıl tanımlayabileceğimize bakalım.

{% set foo = 'foo' %}
{% set foo = [1, 2] %}
{% set foo = {'foo': 'bar'} %}

Bir de kontrol ekleyelim. Navigasyon içerisinde aktif menü başlığını işaretlemek için şöyle bir kullanım yoluna gidebilir ve active ifadesini bir class olarak atayabiliriz.

<a href="{{ page.url }}" class="{% set current_page = (p.active or p.activeChild) ? 'active' : '' %}">...</a>

Filtreler (Filters)

Değişkenlere filtreler aracılığıyla müdahale edebilmekteyiz. Filtre tanımını değişkenden sonra ekleyeceğimiz pipe (|) sembolü ile başlatırız. Birden fazla filtreyi de yine bu şekilde uygulanmasını istediğimiz sıra ile ekleriz. Bu sayede bir filtreden geçen veri bir sonraki filtreye aktarılır.

{% set name = ' [My Twig Code](#)' %}
{{ name|striptags|title }}

Yukarıdaki satırda yer alan name değişkeni strpitags5 ile SGML/XML etiketlerinden arındırılır ve içeriğindeki bitişik boşluklar tek boşluk haline getirilir. Ardından, title6 filtresi devreye girer ve değişkedeki kelime(ler)in ilk harfi büyük hale getirilir. Bu işlemi apply7 ile de gerçekleştirebiliriz.

{% apply striptags|title %}
<a href="#"><i class="far fa-clock"></i> <span>My Twig Code</span></a>
{% endapply %}

Listeleme ile ilgili örnekler oluşturalım.

{% set pagination = {
'text1': 'link1',
'text2': 'link2',
} %}

{% if pagination|length > 1 %}
<ul>
{% for key,value in pagination %}
    <li><a href="{{ value }}">{{ key }}</a></li>
{% endfor %}
</ul>
{% endif %}

Yukarıdaki kodda pagination içeriğinde 1’den fazla değer var ise değerlerin liste halinde sunulması sağlanmaktadır. Liste for döngüsü ile key ve value‘nun ilgili alanlara eklenmesi ile oluşur. Listeler, navigasyon, blog listeleme gibi işlemlerde benzer şekilde kullanılabilir. Şimdi, dizimizi listeleyelim.

{% set list = [1, 'domain.com/page/id?q=1', 'string', 4.0, '5#1', '6'] %}

# örnek 1
{{ list|escape('url')|join(', ') }}

# örnek 2
{% for i in list %}
    <a href="#{{ i }}">{{ i|escape('url') }}</a>{% if not loop.last %}, {% endif %}
{% endfor %}

Her iki örnekte de değerler escape8 filtresi ile işlenmektedir. İlk örnekte list içeriği doğrudan değerlerin arasına virgül koyularak verilecektir. 2. örnekte ise list içeriği loop variable ile birlikte kullanılmakta. Döngü boyunca virgül eklenmekte ancak döngünün son değerinden sonra if9 ile birlikte kullanılan loop.last condition ile virgül eklenmesi önlenmektedir.

Grav temaları oluştururken eklenen görselleri içerikler içerisinde kullanmak istediğimizde dizi olarak veri bize iletilecektir10 11 12 13. Bu tür durumlarda genelde ilk görseli first14 ya da last15 ile çekip kullanırız.

{% if p.media.images %}
    {% set thumbUrl = p.media.images|first.quality(80).cropZoom(800,450).url %}
    <a href="{{p.url}}" title="{{ p.title }}"><img class="card-img rounded-0" src="{{ thumbUrl }}" alt="{{ p.title }}" /></a>
{% endif %}

Örnekte yer alan quality, cropZoom ve url Grav’a ait fonksiyonlar. Şimdilik aşina olmanız yeterli. Grav teması geliştirirken detaylarına değineceğim.

Fonksiyonlar (Functions)

İçeriğin sunumunda fonksiyonlardan16 faydalanabiliriz. Aşağıdaki örnekte range17 fonksiyonu ile belirtilen sayı aralığını döngü olarak kullanabilmekteyiz. Aşağıdaki örnekte print tanımında - (dash) işaretini görmektesiniz18. Dash ile twig tarafından eklenen boşlukların eklenmemesini sağlayabiliriz11.

{% set string = 'number' %}
{% for i in range(0, 3) %}
    {{- i ~ '.' ~ string -}}
{% endfor %}

Zaman ve zaman dilimi ile ilgili örneklere bakalım.

{{ "now"|date('d/m/Y H:i', timezone="Europe/Paris") }}
{{ "now"|date(timezone="UTC") }}

Kendi fonksiyonlarınızı oluşturmak isterseniz Twig Extension19 bölümüne göz atabilirsiniz.

Kontrol Yapıları (Control Structure)

Bir kontrol yapısı, şablondaki mantıksal akışı kontrol eden tüm şeyleri ifade eder. Örneğin, koşullar (if / elseif / else), döngüler (for) için ve bloklar (block) gibi şeyleri bu yapı içerisinde değerlendirebiliriz. Yazının giriş bölümünde de belirttiğim üzere, bu işlemler {% ... %} içinde ele alınır.

If / Elseif / Else

Şablonlar içerisinde şartlı durumları ele almamızı sağlar. starts with, ends with ve matches gibi operatörler20 ve ifadeler ile birlikte21 kullanılabilirler.

{% set word = 'Lorem, ipsum, dolor, sit, amet' %}

{% for w in word|replace({' ':''})|split(',') %}
{% apply spaceless %}
    {% if w starts with 'i' %}
        {{ w|e }} starts with i
    {% endif %}
{% endapply %}

    {% if w ends with 't' %}
        {{ w|e }} ends with t
    {% endif %}

    {% if w matches '/^[a-z]+$/' %}
        {{ w|e }} matched
    {% endif %}

    {% if w|length < 5 and w == 'sit' %}
        {{ w|upper }} length is {{ w|length }}
    {% endif %}
{% endfor %}

For

Twig şablonlarında döngüler için for22 kullanılır.

{% set fruits = ['apple', 'orange', 'citrus'] %}

{% for i in 0..10 %}
    {{ cycle(fruits, i) }}
{% endfor %}

Ancak bu şart değil. in ile de true/false sonuçlarını alabilmekteyiz.

{{ 2 in [1, 2, 3] }}
{{ 'cd' in 'abcde' }}
{% if 2 not in [1, 2, 3] %}
{% if not (3 in [1, 2, 3]) %}
{{ 'cd' in 'abcde' ? 'yes' : 'no' }}
{{ 'cd' in 'abcde' ?: 'no' }} ya da {{ 'cd' in 'abcde' ? '' : 'no' }}
{{ 'cd' in 'abcde' ? 'yes' }} ya da {{ 'cd' in 'abcde' ? 'yes' : '' }}

Template İlişkileri (Include, Extend, Block)

Veriler üzerinde mantıksal işlemler yaptık. Ancak bir şablon sisteminden de bekleyeceğimiz üzere bu işlemleri tekrar tekrar yapmak manasız olacaktır; DRY.

Include

Hazırladığımız yapıları bir diğer dosya içerisine include ile ekleyebiliriz.

{% include 'header.html' %}
{% for post in blog %}
    {{ include('post.html') }}
{% endfor %}
{% include 'footer.html' %}

Include içeriğine de müfahale edilebilmektedir. Örneğin, set ile birlikte include23 edilen dosya içeriğini bir değişkene atayabiliriz.

{% set content %}
    {% include 'template.html' %}
{% endset %}
{# ya da #}
{% set content = include('template.html') %}

Ek olarak, filtreler ile müdahale edebiliriz.

{% filter upper %}
    {% include 'template.html' %}
{% endfilter %}
{# ya da #}
{{ include('template.html')|upper }}

Ancak, include işlemlerini statik olarak düşünmememiz gerekir. Include ettiğimiz dosya içeriğine dışarıdan değerler iletebiliriz.

{% include 'template.html' with {'foo': 'bar'} %}
{# ya da #}
{% set vars = {'foo': 'bar'} %}
{% include 'template.html' with vars %}

{# kontroller de ekleyebiliriz #}
{% include 2 in [1, 2, 3] ? 'content.twig' : 'none.twig' %}

Son olarak, include ile eklediğimiz dosyanın zorunluluk olmadığını da belirtmekte fayda var.

{% include 'sidebar.html' ignore missing %}

Block

Bloklar yer tutucu ve/veya değiştiriciler olarak ifade edilebilirler. Extend başlığı altında örneklerine detaylarına yer vereceğim. Basit bir örnekle ele alalım.

<div id="content">{% block content %}Hello World!{% endblock %}</div>

content adında bir block tanımımız olsun ve bu dosyayı page.twig gibi bir isimle kayıt edelim. Bir başka dosya içerisinden de extend ile çağıralım.

{% extends "block.twig" %}

Bu satırın bulunduğu dosyanın adına da main.twig diyelim. Bu dosya çalıştığında ekrana page.twig içerisindeki Hello World! metnini aktaracaktır. Çünkü biz block için yeni bir değer iletmedik. Şimdi main.twig dosyasına yeni bir satır daha ekleyelim.

{% block content %}It is a World!{% endblock %}

main.twig dosyamızı yeniden çalıştırdığımızda Hello World! yerine It is a World! metninin geldiğini göreceksiniz. Çünkü bu metni kapsayan block için yeni bir değer göndermiş olduk.

Extend

Az önceki örnekte de temel bir şekilde bahsi geçtiği üzere extend24 şablonları genişletmemiz, içeriklerine müdahale edebilmemizi sağlar. Çoklu kullanılamazlar. Yani, child template olarak ifade ettiğimiz ve block'lara yeni değerler gönderdiğimiz şablon içerisinde25 tek bir extend tanımı yer alabilir. Ancak, çoklu yapılar için use ile çözüm sağlanabilmektedir. Extend ve use için Twig: Örnek Şablon başlığına bakabilirsiniz26 27.

Makrolar

Makrolar, şablonlar içerisinde sıklıkla yinelenen alanları kendimizi tekrar etmeden dinamik bir şekilde kullanabilmemiz sağlarlar. Örnek olarak form elemanlarını değerlendirebiliriz. E-bülten, iletişim, teklif formu gibi farklı amaçlar için oluşturduğumuz formlar içerisinde çoğu zaman aynı tür, özellik ve niteliklerde elemanlar mevcut olacaktır. Bu gibi durumlarda macro ile çözümler üretebilmekteyiz.

{% macro input(name, value, type = "text", size = 20) %}
    <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}" />
{% endmacro %}

{% macro textarea(name, value, rows = 10, cols = 40) %}
    <textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols }}">{{ value|e }}</textarea>
{% endmacro %}

Makroları import ile çağırabilmekteyiz.

{% import "forms.twig" as forms %}

Twig: Örnek Şablon

Aşağıdaki dosyayı base.html.twig olarak kayıt edebiliriz.

<!DOCTYPE html>
<html>
    <head>
    {% block head %}
        {% block head_css %}
            {% do assets.add('theme://assets/css/style.css') %}
            {{ assets.css()|raw }}
        {% endblock %}
        <title>{% block title %}{% endblock %}</title>
    {% endblock %}
    </head>
    <body>
        <div id="content">{% block content %}{% endblock %}</div>

        {% use "blocks.twig" %}

        <div id="footer">
            {% block footer %}
                &copy; Copyright 2020 by x</a>.
            {% endblock %}
        </div>
        {% block body_js %}
            {% do assets.addJs('theme://assets/js/main.js') %}
            {{ assets.js()|raw }}
        {% endblock %}
    </body>
</html>

Bu kodumuzu da page.html.twig isimli bir dosya olarak kayıt altına alabiliriz.

{% extends "base.html" %}

{% block title %}Index{% endblock %}
{% block head %}
    {{ parent() }}
    {% block head_css %}
        <style type="text/css">
            .important { color: #336699; }
        </style>
    {% endblock %}
{% endblock %}
{% block content %}
    <h1>Index</h1>
    <p class="important">
        It is a homepage.
    </p>
{% endblock %}

Etiketler, fonksyonlar, filtreler ve operatörlerin tamamına Reference28 bölümünden ulaşabilirsiniz.

İleri Okumalar ve Örnekler