Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Writing phtml Templates: the Escaper, Best Practices, No Raw Output Without Escaping

Writing phtml Templates: the Escaper, Best Practices, No Raw Output Without Escaping

~7 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

At first glance, a .phtml template in Hyvä looks like plain PHP mixed into HTML - and for the most part, it is. The key difference from "quickly mixing some PHP into HTML" is the consistent use of the escaper for every dynamic output.

The escaper: $escaper

Every standard template gets an instance of \Magento\Framework\Escaper made available as $escaper. The most important methods:

  • escapeHtml($string) - for regular text content (the standard case, needed most often).
  • escapeHtmlAttr($string) - for values inside HTML attributes, e.g. alt or title.
  • escapeUrl($string) - for URLs, e.g. in href or src.
  • escapeJs($string) - for values embedded in JavaScript inside a <script> block.
Example: correct escaping
<?php
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Mironsoft\TeamPage\ViewModel\TeamMembers $teamViewModel */
?>
<?php foreach ($teamViewModel->getTeamMembers() as $member): ?>
    <article>
        <h3><?= $escaper->escapeHtml($member['name']) ?></h3>
        <img src="<?= $escaper->escapeUrl($member['photo_url']) ?>"
             alt="<?= $escaper->escapeHtmlAttr($member['name']) ?>">
    </article>
<?php endforeach; ?>

Achtung: Raw output like <?= $member['name'] ?> without the escaper is an XSS vulnerability as soon as the value comes (even partly) from user input or the admin - and even for seemingly safe values, it's simply the wrong habit. In this project: every dynamic output goes through the matching escaper, no exceptions.

@var annotations at the top of the file

At the top of every template you'll find /** @var Type $variable */ comments for $block, $escaper, and every bound ViewModel. That's not just documentation - PhpStorm and PHPStan use these annotations for autocompletion and static analysis, which wouldn't otherwise be possible in plain .phtml files without real type declarations.

Keep template logic minimal

A template should essentially just output what a ViewModel or block supplies it with - loops and simple conditions are normal, but complex business logic belongs in the ViewModel, not the template. A good rule of thumb: if a condition needs more than a one-line PHP expression, it probably belongs as its own method in the ViewModel.

<?php // Better avoided: complex logic directly in the template
<?php if (count(array_filter($members, fn($m) => $m['category'] === 'design')) > 0): ?>

<?php // Better: a finished, descriptive method on the ViewModel
<?php if ($teamViewModel->hasDesignTeamMembers()): ?>

Keep Tailwind classes readable

When an element has many utility classes, it's worth formatting the opening tag across several lines instead of producing one very long line - that keeps future diffs small and the classes easy to scan. Chapters 11-13 cover Tailwind in Hyvä templates in detail.