Skip to content
beginner Phase 24 · Module Frontend

Creating Blocks and Templates

Practical guide to creating Magento 2 block classes and .phtml templates: data passing, variable output, escaping, and template inheritance

45m
0 problems
Topic Progress 0%

Creating Block Classes

Basic Block

<?php
namespace Amazon\Prep\Block;

use Magento\Framework\View\Element\Template;

class PrepInfo extends Template
{
    public function __construct(
        Template\Context $context,
        array $data = []
    ) {
        parent::__construct($context, $data);
    }

    public function getPrepTitle(): string
    {
        return $this->getData('title') ?? 'Prep Information';
    }

    public function getPrepItems(): array
    {
        return $this->getData('items') ?? [];
    }
}

Block with Dependencies

<?php
namespace Amazon\Prep\Block;

use Magento\Framework\View\Element\Template;
use Amazon\Prep\Model\ResourceModel\Item\CollectionFactory;

class ItemList extends Template
{
    public function __construct(
        Template\Context $context,
        private CollectionFactory $collectionFactory,
        array $data = []
    ) {
        parent::__construct($context, $data);
    }

    public function getItems(): array
    {
        return $this->collectionFactory->create()
            ->addFieldToSelect('*')
            ->setPageSize(10)
            ->getItems();
    }

    public function getItemCount(): int
    {
        return count($this->getItems());
    }

    public function formatPrice(float $price): string
    {
        return $this->helper('Magento\Framework\View\Element\AbstractBlock')
            ->formatPrice($price);
    }
}

Block with Registry Access

<?php
namespace Amazon\Prep\Block;

use Magento\Framework\View\Element\Template;

class ProductPrep extends Template
{
    public function __construct(
        Template\Context $context,
        private \Magento\Framework\Registry $registry,
        array $data = []
    ) {
        parent::__construct($context, $data);
    }

    public function getCurrentProduct(): ?\Magento\Catalog\Model\Product
    {
        return $this->registry->registry('current_product');
    }

    public function getProductSku(): string
    {
        $product = $this->getCurrentProduct();
        return $product ? $product->getSku() : '';
    }
}

Writing .phtml Templates

Template File Structure

Place templates in view/frontend/templates/:

view/frontend/templates/
├── prep_info.phtml
├── item/
│   ├── list.phtml
│   └── view.phtml
└── widget/
    └── custom.phtml

Basic Template

<!-- prep_info.phtml -->
<?php
/** @var Amazon\Prep\Block\PrepInfo $block */
$title = $block->getPrepTitle();
$items = $block->getPrepItems();
?>
<div class="prep-info">
    <h2><?= $block->escapeHtml($title) ?></h2>
    <?php if (!empty($items)): ?>
        <ul class="prep-list">
            <?php foreach ($items as $item): ?>
                <li><?= $block->escapeHtml($item) ?></li>
            <?php endforeach; ?>
        </ul>
    <?php else: ?>
        <p><?= __('No prep items available.') ?></p>
    <?php endif; ?>
</div>

Accessing Block Methods

In templates, $block refers to the current block:

// Call block methods
$block->getProductName();
$block->formatPrice(29.99);
$block->escapeHtml($text);

// Access layout
$layout = $block->getLayout();

// Access request
$request = $block->getRequest();
$param = $request->getParam('id');

// Access URL builder
$url = $block->getUrl('vendor/module/action', ['id' => 1]);
$baseUrl = $block->getBaseUrl();

// Access store
$storeId = $block->getStoreManager()->getStore()->getId();
$storeName = $block->getStoreManager()->getStore()->getName();

// Access helper
$helper = $block->helper('Vendor\Module\Helper\Data');

Template Variables from Layout

<block class="Amazon\Prep\Block\PrepInfo"
       name="prep.info"
       template="Amazon_Prep::prep_info.phtml">
    <arguments>
        <argument name="title" xsi:type="string">Custom Title</argument>
        <argument name="show_header" xsi:type="number">1</argument>
    </arguments>
</block>

Access in block:

public function shouldShowHeader(): bool
{
    return (bool)$this->getData('show_header');
}

Output Escaping

Why Escape Output?

Unescaped output enables XSS (Cross-Site Scripting) attacks. Always escape user-provided or dynamic data.

Escape Functions

// Text escaping (removes all HTML)
echo $block->escapeHtml($text);

// Allow specific HTML tags
echo $block->escapeHtml($richText, ['b', 'i', 'em', 'strong', 'p', 'br']);

// URL escaping
echo $block->escapeUrl($url);

// CSS escaping
echo $block->escapeCss($cssValue);

// JavaScript escaping
echo $block->escapeJs($jsString);

// HTML attribute escaping
echo $block->escapeHtmlAttr($attributeValue);

// Quote escaping
echo $block->escapeQuote($stringValue);

Escaping Examples

<!-- Product name (user input) -->
<h1><?= $block->escapeHtml($product->getName()) ?></h1>

<!-- URL (could be malicious) -->
<a href="<?= $block->escapeUrl($product->getProductUrl()) ?>">
    <?= $block->escapeHtml($product->getName()) ?>
</a>

<!-- Image src -->
<img src="<?= $block->escapeUrl($block->getViewFileUrl('images/logo.png')) ?>"
     alt="<?= $block->escapeHtmlAttr($product->getName()) ?>">

<!-- Style attribute -->
<div style="<?= $block->escapeCss($customStyle) ?>">
    Content
</div>

<!-- JavaScript variable -->
<script>
var productName = '<?= $block->escapeJs($product->getName()) ?>';
</script>

<!-- Rich text with allowed tags -->
<div class="description">
    <?= $block->escapeHtml($product->getDescription(), ['p', 'br', 'strong', 'em']) ?>
</div>

Escaping in Different Contexts

Context Function
HTML body escapeHtml()
HTML attributes escapeHtmlAttr()
URLs escapeUrl()
CSS escapeCss()
JavaScript escapeJs()
JSON json_encode() (PHP)

Common Mistakes

// WRONG: No escaping
echo $product->getName();

// WRONG: Wrong escaping function
echo $block->escapeHtml($product->getUrl());  // URL in HTML context

// RIGHT: Match escaping to context
echo $block->escapeHtml($product->getName());
echo $block->escapeUrl($product->getUrl());

Child Blocks and Template Inheritance

Rendering Child Blocks

In Layout XML

<block class="Amazon\Prep\Block\Container" name="container">
    <block class="Amazon\Prep\Block\Header" name="header" template="header.phtml"/>
    <block class="Amazon\Prep\Block\Content" name="content" template="content.phtml"/>
    <block class="Amazon\Prep\Block\Footer" name="footer" template="footer.phtml"/>
</block>

In Parent Template

<!-- container.phtml -->
<div class="wrapper">
    <?= $block->getChildBlock('header')->toHtml() ?>
    <?= $block->getChildBlock('content')->toHtml() ?>
    <?= $block->getChildBlock('footer')->toHtml() ?>
</div>

Render All Children

<!-- container.phtml -->
<div class="wrapper">
    <?= $block->getChildHtml() ?>
</div>

<!-- With specific order -->
<div class="wrapper">
    <?= $block->getChildHtml('header') ?>
    <?= $block->getChildHtml('content') ?>
    <?= $block->getChildHtml('footer') ?>
</div>

Adding Child Blocks Programmatically

// In block _prepareLayout()
public function _prepareLayout(): void
{
    $this->setChild('custom_block',
        $this->getLayout()->createBlock(
            \Amazon\Prep\Block\Custom::class,
            'custom_block_name'
        )
    );
    parent::_prepareLayout();
}

// In template
echo $block->getChildBlock('custom_block')->toHtml();

Template Inheritance

<!-- parent.phtml -->
<html>
<head><title><?= $block->pageTitle ?></title></head>
<body>
    <header><?= $block->getChildHtml('header') ?></header>
    <main><?= $block->getChildHtml('content') ?></main>
    <footer><?= $block->getChildHtml('footer') ?></footer>
</body>
</html>

<!-- child.phtml (extends parent) -->
<?php $this->extend('parent.phtml') ?>

<?php $this->blockContent('header') ?>
    <h1><?= $block->escapeHtml($block->getTitle()) ?></h1>
<?php $this->blockContent() ?>

<?php $this->blockContent('content') ?>
    <div class="main-content">
        <?= $block->getChildHtml() ?>
    </div>
<?php $this->blockContent() ?>

<?php $this->blockContent('footer') ?>
    <p>© 2024 Amazon Prep</p>
<?php $this->blockContent() ?>

Block Caching with Templates

// Block class
public function getCacheKey(): string
{
    return 'amazon_prep_list_' . $this->getStoreId();
}

public function getCacheLifetime(): ?int
{
    return 3600; // 1 hour
}

public function getCacheTags(): array
{
    return ['amazon_prep_list'];
}

Quiz

1. What variable refers to the current block in a .phtml template?

Question 1 options

2. Which function escapes HTML to prevent XSS attacks?

Question 2 options

3. How do you render a child block named 'header' in a template?

Question 3 options

Flashcards

Question

What is the base class for template blocks?

Answer

Magento\Framework\View\Element\Template

Question

How do you access a block method in a template?

Answer

$block->methodName()

Question

What escapes HTML for safe output?

Answer

$block->escapeHtml($text)

Question

How do you render all child blocks?

Answer

$block->getChildHtml()

Question

Where do template files go?

Answer

view/frontend/templates/

Revision Notes

Key Takeaways

  • 1. Blocks extend Template and prepare data for .phtml templates
  • 2. Always escape output with escapeHtml(), escapeUrl(), escapeJs() etc.
  • 3. Use $block to access block methods in templates
  • 4. Child blocks are rendered with getChildHtml('name')
  • 5. Block caching prevents re-rendering unchanged content

Interview Tips

  • Explain the block → template data flow
  • Know all escaping functions and when to use each
  • Be ready to create a block with template and child blocks
  • Discuss block caching strategies

Cheat Sheet

Block: extends Template
Template: $block->methodName()

Escaping:
  escapeHtml()     → HTML body
  escapeUrl()      → URLs
  escapeJs()       → JavaScript
  escapeCss()      → CSS
  escapeHtmlAttr() → attributes

Child blocks:
  $block->getChildHtml('name')
  $block->getChildHtml()  // all