/**
* TEMPLATE BEST PRACTICES - PHTML Templates
* Patterns to follow and avoid in Magento 2 templates.
*/
export interface TemplatePattern {
pattern: string;
message: string;
replacement: string;
severity: number;
autoFixable: boolean;
}
export const DEPRECATED_PATTERNS: Record<string, TemplatePattern> = {
'this_in_template': {
pattern: '$this->',
message: 'The use of $this in templates is deprecated. Use $block instead.',
replacement: '$block->',
severity: 8,
autoFixable: true
},
'helper_in_template': {
pattern: '$this->helper(',
message: 'The use of helpers in templates is discouraged. Use ViewModel instead.',
replacement: `// Step 1: Create a ViewModel class
// File: Vendor/Module/ViewModel/MyViewModel.php
<?php
namespace Vendor\\Module\\ViewModel;
use Magento\\Framework\\View\\Element\\Block\\ArgumentInterface;
class MyViewModel implements ArgumentInterface
{
public function getSomething(): string
{
return 'value';
}
}
// Step 2: Add to layout XML
<block class="Magento\\Framework\\View\\Element\\Template" name="my.block" template="Vendor_Module::template.phtml">
<arguments>
<argument name="viewModel" xsi:type="object">Vendor\\Module\\ViewModel\\MyViewModel</argument>
</arguments>
</block>
// Step 3: Use in template
<?php
/** @var \\Vendor\\Module\\ViewModel\\MyViewModel $viewModel */
$viewModel = $block->getData('viewModel');
?>
<?= $escaper->escapeHtml($viewModel->getSomething()) ?>`,
severity: 8,
autoFixable: false
},
};
export const FORBIDDEN_PATTERNS: Record<string, TemplatePattern> = {
'object_manager': {
pattern: 'ObjectManager::getInstance()',
message: 'Direct ObjectManager usage is forbidden in templates. Use dependency injection via Block or ViewModel.',
replacement: `// Use dependency injection instead:
// 1. Inject dependency in Block constructor
// 2. Create a public method to access the data
// 3. Call $block->getMethodName() in template
// Or use ViewModel (preferred):
// 1. Create ViewModel with injected dependencies
// 2. Attach ViewModel to block via layout XML
// 3. Access via $block->getData('viewModel')`,
severity: 10,
autoFixable: false
},
'direct_database': {
pattern: 'getConnection()',
message: 'Direct database access in templates is forbidden. Use repositories or collections.',
replacement: 'Use a ViewModel or Block method that accesses data through repositories',
severity: 10,
autoFixable: false
},
};
export const PREFERRED_PATTERNS: Record<string, TemplatePattern> = {
'short_echo': {
pattern: '<?= $value ?>',
message: 'Use short echo syntax in templates',
replacement: '<?= $value ?> instead of <?php echo $value ?>',
severity: 6,
autoFixable: true
},
'escaper_injection': {
pattern: '$escaper->',
message: 'Use injected $escaper for escaping instead of $block->escape*()',
replacement: `// In template, $escaper is available from Magento 2.4+
// For older versions or custom templates, add to block:
<?php
/** @var \\Magento\\Framework\\Escaper $escaper */
/** @var \\Magento\\Framework\\View\\Element\\Template $block */
?>
// Then use:
<?= $escaper->escapeHtml($value) ?>`,
severity: 5,
autoFixable: false
},
};
/**
* Complete template structure example
*/
export const TEMPLATE_STRUCTURE_EXAMPLE = `<?php
/**
* Template description
*
* @var \\Magento\\Framework\\View\\Element\\Template $block
* @var \\Magento\\Framework\\Escaper $escaper
* @var \\Vendor\\Module\\ViewModel\\MyViewModel $viewModel
*/
$viewModel = $block->getData('viewModel');
?>
<div class="my-component">
<h2><?= $escaper->escapeHtml($block->getTitle()) ?></h2>
<?php if ($viewModel->hasItems()): ?>
<ul class="items">
<?php foreach ($viewModel->getItems() as $item): ?>
<li class="item">
<a href="<?= $escaper->escapeUrl($item->getUrl()) ?>">
<?= $escaper->escapeHtml($item->getName()) ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p><?= $escaper->escapeHtml(__('No items found.')) ?></p>
<?php endif; ?>
<script type="text/x-magento-init">
{
"*": {
"Vendor_Module/js/component": {
"config": <?= /* @noEscape */ $block->getJsLayout() ?>
}
}
}
</script>
</div>`;
/**
* ViewModel structure example
*/
export const VIEWMODEL_EXAMPLE = `<?php
declare(strict_types=1);
namespace Vendor\\Module\\ViewModel;
use Magento\\Framework\\View\\Element\\Block\\ArgumentInterface;
use Vendor\\Module\\Api\\ItemRepositoryInterface;
use Magento\\Framework\\Api\\SearchCriteriaBuilder;
class ItemList implements ArgumentInterface
{
private ItemRepositoryInterface $itemRepository;
private SearchCriteriaBuilder $searchCriteriaBuilder;
public function __construct(
ItemRepositoryInterface $itemRepository,
SearchCriteriaBuilder $searchCriteriaBuilder
) {
$this->itemRepository = $itemRepository;
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
}
public function getItems(): array
{
$searchCriteria = $this->searchCriteriaBuilder
->addFilter('is_active', 1)
->create();
return $this->itemRepository->getList($searchCriteria)->getItems();
}
public function hasItems(): bool
{
return count($this->getItems()) > 0;
}
}`;
/**
* Layout XML example for ViewModel
*/
export const LAYOUT_VIEWMODEL_EXAMPLE = `<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Magento\\Framework\\View\\Element\\Template"
name="vendor.module.items"
template="Vendor_Module::items.phtml">
<arguments>
<argument name="viewModel" xsi:type="object">
Vendor\\Module\\ViewModel\\ItemList
</argument>
</arguments>
</block>
</referenceContainer>
</body>
</page>`;
/**
* Get all deprecated patterns
*/
export function getDeprecatedPatterns(): Record<string, TemplatePattern> {
return DEPRECATED_PATTERNS;
}
/**
* Get all forbidden patterns
*/
export function getForbiddenPatterns(): Record<string, TemplatePattern> {
return FORBIDDEN_PATTERNS;
}
/**
* Check if code contains deprecated patterns
*/
export function checkForDeprecatedPatterns(code: string): Array<{pattern: string, info: TemplatePattern}> {
const violations: Array<{pattern: string, info: TemplatePattern}> = [];
for (const [key, pattern] of Object.entries(DEPRECATED_PATTERNS)) {
if (code.includes(pattern.pattern)) {
violations.push({ pattern: key, info: pattern });
}
}
for (const [key, pattern] of Object.entries(FORBIDDEN_PATTERNS)) {
if (code.includes(pattern.pattern)) {
violations.push({ pattern: key, info: pattern });
}
}
return violations;
}