Skip to content

Twig templates ​

Twig is Blade's cousin / JSX's distant relative. Escapes by default, extends layouts, and lives under templates/.

Layout + page ​

twig
{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
  <head>
    <title>{% block title %}App{% endblock %}</title>
    {% block stylesheets %}{% endblock %}
  </head>
  <body>
    {% block body %}{% endblock %}
    {% block javascripts %}{% endblock %}
  </body>
</html>
twig
{# templates/posts/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}Posts{% endblock %}

{% block body %}
  <h1>Posts</h1>
  <ul>
    {% for post in posts %}
      <li>
        <a href="{{ path('app_posts_show', { id: post.id }) }}">
          {{ post.title }}
        </a>
      </li>
    {% else %}
      <li>No posts yet.</li>
    {% endfor %}
  </ul>
{% endblock %}

Comments for the code above:

  • {{ post.title }} is escaped HTML - like Blade {{ }} or React text children
  • {% … %} is logic/tags; {{ … }} prints
  • path('route_name', {…}) builds URLs from named routes

Escaping vs raw ​

twig
{{ userBio }}           {# escaped - default, good #}
{{ userBio|raw }}       {# dangerous if user-controlled #}

Same rule as Blade {!! !!} - only raw when you trust the content.

Includes & embeds ​

twig
{% include 'partials/_nav.html.twig' %}

Macros / embeds exist for reusable chunks. Start with include; grow when it hurts.

Forms in Twig ​

When you use the Form component (next chapters), you'll see:

twig
{{ form_start(form) }}
  {{ form_row(form.title) }}
  {{ form_row(form.body) }}
  <button>Save</button>
{{ form_end(form) }}

That's CSRF + labels + errors wired for you.

FE mental map ​

Vue / React / BladeTwig
layout component{% extends %}
v-for / .map{% for %}
{{ }} text{{ }} (escaped)
v-if{% if %}
named routes helperpath() / url()

Next: how CSS/JS land in the page without inventing a second build religion.

Best,

-- ł.

Continues after /lab/php-tutorial-for-fe-devs/