Automating boilerplate for PHP teams
PHP teams write the same boilerplate patterns every day: ViewModels with constructor property promotion, service interfaces with repository conventions, PHPDoc blocks for Magento classes. Live Templates and Postfix Templates in PhpStorm reduce these repetitive writing tasks to a few keystrokes, and can be shared as a team library.
Table of Contents
- 1. Live Templates vs. Postfix Templates: understanding the difference
- 2. Creating your own Live Templates: variables and tabstops
- 3. PHP 8.4 boilerplate: constructor promotion and enums
- 4. Magento templates: ViewModels, plugins, and repositories
- 5. Postfix Templates: chain shortcuts for everyday use
- 6. Exporting and sharing team libraries
- 7. Hyva templates: Alpine.js and Tailwind snippets
- 8. Live Templates compared: when to use what
- 9. Summary
- 10. FAQ
1. Live Templates vs. Postfix Templates: understanding the difference
Live Templates are text blocks activated via an abbreviation. You type the abbreviation, press Tab, and PhpStorm replaces it with the full template text. The cursor automatically jumps to the defined tabstops, where the variable parts are typed in. Pressing Tab again moves the cursor to the next tabstop. Live Templates suit patterns you begin from an empty state: a new class skeleton, a PHPDoc block, or an if construct.
Postfix Templates work the other way around: you first write an expression and then append the abbreviation with a dot. From $collection.foreach you get a complete foreach loop with $collection already inserted as the iterator. From $value.notnull you get a null check. Postfix Templates are especially valuable when you want to wrap an already-typed expression in a control structure without navigating back and rewriting it. PhpStorm ships with an extensive default library for PHP that can be extended with your own templates.
2. Creating your own Live Templates: variables and tabstops
Under Settings → Editor → Live Templates you create new groups and templates. Each template has an abbreviation, a description text, and the template text. Inside the template text, $VARIABLENAME$ expressions mark the tabstops. The variable $END$ is reserved and indicates where the cursor should land after the last Tab step. For a tabstop without a variable, a single $END$ is enough.
Variables can be pre-filled with expressions: fileNameWithoutExtension() returns the file name without its extension, phpClassName() returns the class name derived from the file name, date("yyyy-MM-dd") returns the current date. The Skip if defined flag prevents the cursor from stopping at a variable that already has a value from an expression. The context scope (Applicable In) determines in which file types and scopes (class, statement, expression, comment) the template can be triggered.
<?php
/**
* Live Template abbreviation: vmclass
* Applicable: PHP → Class
* Description: Magento ViewModel class with constructor property promotion
*
* Template text:
* ---------------------------------------------------------------
* declare(strict_types=1);
*
* namespace $NAMESPACE$;
*
* use Magento\Framework\View\Element\Block\ArgumentInterface;
*
* /**
* * $CLASSNAME$ ViewModel
* *
* * @package $NAMESPACE$
* * /
* class $CLASSNAME$ implements ArgumentInterface
* {
* public function __construct(
* $END$
* ) {
* }
* }
* ---------------------------------------------------------------
* Variables:
* $NAMESPACE$ → Expression: phpNamespace() | Skip if defined: no
* $CLASSNAME$ → Expression: phpClassName() | Skip if defined: yes
* $END$ → reserved (cursor end position)
*/
// Result after expanding "vmclass" + Tab:
declare(strict_types=1);
namespace Mironsoft\Catalog\ViewModel;
use Magento\Framework\View\Element\Block\ArgumentInterface;
/**
* ProductList ViewModel
*
* @package Mironsoft\Catalog\ViewModel
*/
class ProductList implements ArgumentInterface
{
public function __construct(
// Cursor lands here
) {
}
}
3. PHP 8.4 boilerplate: constructor promotion and enums
PHP 8.4 brings property hooks, readonly classes, and asymmetric visibility. For teams moving to PHP 8.4, it is worth creating Live Templates for the most common new patterns. A template for a readonly class with constructor property promotion reduces writing this structure from roughly 20 lines to a single Tab expand. Enum templates with from() and tryFrom() methods cover another frequent boilerplate case.
Particularly useful is a template for PHPDoc blocks with @param lines that are pre-filled automatically from the method name via an expression. PhpStorm does generate PHPDoc automatically when typing /** above a method, but for class-level comments with specific tags (@since, @api) or for interfaces with complete documentation, custom templates are considerably faster. Another useful template: declare(strict_types=1); as the first statement. The abbreviation dstrict expands into two lines and places the cursor afterward, which saves time on every new PHP file.
<?php
// Live Template abbreviation: enumbs (backed string enum with labels)
// Applicable: PHP → File
declare(strict_types=1);
namespace Mironsoft\Catalog\Enum;
/**
* OrderStatus backed enum
*
* Provides type-safe order status with display labels.
*/
enum OrderStatus: string
{
case Pending = 'pending';
case Processing = 'processing';
case Complete = 'complete';
case Canceled = 'canceled';
/**
* Return human-readable label for the status.
*/
public function label(): string
{
return match($this) {
self::Pending => 'Pending',
self::Processing => 'Processing',
self::Complete => 'Complete',
self::Canceled => 'Canceled',
};
}
/**
* Return all statuses as associative array for select fields.
*
* @return array<string, string>
*/
public static function toSelectOptions(): array
{
return array_column(
array_map(
fn(self $case) => ['value' => $case->value, 'label' => $case->label()],
self::cases()
),
'label',
'value'
);
}
}
4. Magento templates: ViewModels, plugins, and repositories
Magento 2 has recurring patterns that map excellently to Live Templates. A plugin template (abbreviation: mgplugin) generates the skeleton of an interceptor class with aroundMethodName and afterMethodName methods that include the correct callable $proceed parameter. A repository template (abbreviation: mgrepo) generates a repository class with getById, save, delete, and getList methods that use the respective model interface as the return type.
For the frequent combination of a di.xml entry and the associated PHP class, a Live Template unfortunately is not enough; PhpStorm File Templates are better suited for that. But for pure PHP boilerplate, Live Templates are the fastest solution. A ViewModel template with a preconfigured ArgumentInterface and constructor property promotion is available to all project members immediately after a one-time setup, once the template library is shared via a Settings export.
<?php
// Live Template abbreviation: mgplugin
// Generates a Magento plugin skeleton
declare(strict_types=1);
namespace Mironsoft\Catalog\Plugin;
use Magento\Catalog\Model\Product;
/**
* ProductPlugin intercepts product-related methods.
*
* Register in etc/di.xml:
* <type name="Magento\Catalog\Model\Product">
* <plugin name="mironsoft_catalog_product_plugin"
* type="Mironsoft\Catalog\Plugin\ProductPlugin" />
* </type>
*/
class ProductPlugin
{
public function __construct(
private readonly \Psr\Log\LoggerInterface $logger,
) {
}
/**
* After plugin: called after Product::getName().
*
* @param Product $subject The original product instance
* @param string $result The original return value
* @return string The modified return value
*/
public function afterGetName(Product $subject, string $result): string
{
// Modify result or add side effects here
return $result;
}
/**
* Around plugin: wraps Product::isSalable() with custom logic.
*
* @param Product $subject The original product instance
* @param callable $proceed Call $proceed() to invoke the original method
* @return bool
*/
public function aroundIsSalable(Product $subject, callable $proceed): bool
{
// Custom pre-logic
$result = $proceed();
// Custom post-logic
return $result;
}
}
5. Postfix Templates: chain shortcuts for everyday use
PhpStorm ships with a number of useful default Postfix Templates for PHP: .var wraps an expression in a $variable = ... assignment, .not negates a boolean expression, .return turns the expression into a return statement, .null generates an === null check. These shortcuts are especially efficient when you want to convert an expression into a control structure while typing, without navigating back.
Custom Postfix Templates can be created under Settings → Editor → General → Postfix Completion. One useful custom template for Magento: .escHtml wraps an expression in $block->escapeHtml($EXPR$). Another: .logger generates $this->logger->info($EXPR$). These templates save several cursor movements on every use, and at the same time make sure that no unsafe string output ends up without escaping, a common security mistake in Magento templates.
6. Exporting and sharing team libraries
PhpStorm stores Live Templates in XML files in the configuration directory, whose location varies by operating system (on Linux: ~/.config/JetBrains/PhpStorm[version]/templates/). For teams, exporting via File → Manage IDE Settings → Export Settings with the Live Templates checkbox selected is recommended. The exported ZIP file can be checked into the project repository (.idea/templates/ or a dedicated dev/phpstorm/ folder), so new team members can import the library immediately.
Alternatively, PhpStorm offers the Settings Sync feature, which synchronizes templates across all devices via a JetBrains account. For teams with multiple developers this is convenient, as long as everyone is allowed to use the same account, which is often not the case in professional environments. Manual XML export into the Git repository is therefore the recommended approach for professional PHP teams: versioned, traceable, and independent of external services.
7. Hyva templates: Alpine.js and Tailwind snippets
For Hyva projects with .phtml templates, Live Templates for the most common Alpine.js patterns pay off. One template (abbreviation: axdata) generates an x-data attribute with an empty JavaScript object expression and an x-init call. Another (abbreviation: axcomponent) generates the full Alpine.js component registration with a document.addEventListener('alpine:init') wrapper and an Alpine.data() call, including the required $hyvaCsp->registerInlineScript() call, which is mandatory in Hyva for CSP compliance.
Tailwind class combinations that recur frequently in a project can likewise be saved as Live Templates. Instead of typing the full button class combination every time (inline-flex items-center justify-center gap-2 rounded-xl font-semibold transition-colors bg-fuchsia-600 text-white hover:bg-fuchsia-700), the abbreviation btnprimary is enough. This is especially useful in templates where CSS classes are not imported from component libraries but written directly in the markup.
<?php
// Live Template abbreviation: hyvacomponent
// Applicable: HTML → inside a script tag scope
// Generates a complete Hyva/Alpine.js component registration
/** @var \Hyva\Theme\Model\ViewModelRegistry $viewModels */
/** @var \Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */
?>
<script>
// Alpine.js component registration for Hyva Theme
// Registered via alpine:init event to ensure Alpine is ready
document.addEventListener('alpine:init', () => {
Alpine.data('productTabs', () => ({
activeTab: 'description',
init() {
// Initialization logic here
},
switchTab(tabName) {
this.activeTab = tabName;
},
isActive(tabName) {
return this.activeTab === tabName;
},
}));
});
</script>
<?= /* @noEscape */ $hyvaCsp->registerInlineScript() ?>
<!-- Usage in template -->
<div x-data="productTabs()" class="mt-8">
<nav class="flex gap-2 border-b border-slate-200 mb-6">
<button @click="switchTab('description')"
:class="isActive('description') ? 'border-b-2 border-fuchsia-600 text-fuchsia-700' : 'text-slate-600'"
class="px-4 py-2 font-semibold text-sm transition-colors">
<?= $block->escapeHtml(__('Description')) ?>
</button>
<button @click="switchTab('attributes')"
:class="isActive('attributes') ? 'border-b-2 border-fuchsia-600 text-fuchsia-700' : 'text-slate-600'"
class="px-4 py-2 font-semibold text-sm transition-colors">
<?= $block->escapeHtml(__('Attributes')) ?>
</button>
</nav>
<div x-show="isActive('description')" x-transition>
<?= $block->getChildHtml('description') ?>
</div>
<div x-show="isActive('attributes')" x-transition>
<?= $block->getChildHtml('additional') ?>
</div>
</div>
8. Live Templates compared: when to use what
The distinction between Live Templates, Postfix Templates, File Templates, and Code Generation in PhpStorm is not always intuitive. The following overview helps decide which tool fits which use case.
| Tool | Trigger | Strength | Typical use |
|---|---|---|---|
| Live Templates | Abbreviation + Tab | Multi-line boilerplate | New classes, methods, PHPDoc |
| Postfix Templates | Expression + .abbreviation | Wrapping an expression | return, null check, foreach, var |
| File Templates | New file | Complete file skeleton | New PHP class with namespace |
| Code Generation | Alt+Insert | Getter/setter, override | Generate methods from an interface |
| Surround With | Selection + Ctrl+Alt+T | Wrapping a selection | try/catch, if, while around code |
A common mistake when using Live Templates: an overly broad context configuration. If a template is active for all PHP contexts, the abbreviation appears as an autocomplete suggestion in inappropriate situations, for example inside strings or comments. Always choose the narrowest matching context: PHP → Statement for statements, PHP → Class Member for methods and properties. That significantly improves the signal-to-noise ratio in the autocomplete list.
Mironsoft
Magento 2, Hyva Themes, and PHP 8.4 development
Want to automate PHP boilerplate for your team?
We build project-specific Live Template libraries for Magento 2, Hyva Themes, and PHP 8.4, and integrate them into your team workflow, with versioning and an import guide.
Template library
Live Templates for ViewModels, plugins, repositories, and Hyva components
Team export
XML export into the Git repository with import documentation for new developers
PHP 8.4 patterns
Property hooks, readonly classes, and asymmetric visibility as snippets
9. Summary
Live Templates and Postfix Templates in PhpStorm are among the most underestimated productivity tools for PHP teams. Once set up, they significantly reduce the amount of boilerplate code developers have to write, without switching IDEs, setting up external snippet tools, or copying code. The template library grows with the project: every new recurring pattern typed more than three times is a candidate for a Live Template.
For Magento 2 teams working with Hyva Themes and PHP 8.4, the most frequent candidates are: ViewModel classes, plugins, repositories, PHPDoc blocks with Magento-specific tags, Alpine.js component registrations, and Tailwind class combinations for Hyva templates. Team export via Git makes sure all developers use the same standard boilerplate, and newly joining colleagues become productive immediately.
Live Templates in PhpStorm, the essentials at a glance
Live Templates
Abbreviation + Tab for multi-line boilerplate. Pre-fill variables with expressions (phpClassName, fileNameWithoutExtension, date). Configure context narrowly.
Postfix Templates
Expression + .abbreviation to wrap control structures: .return, .null, .foreach, .var. Create custom templates in Settings → General → Postfix Completion.
Team sharing
XML export into the Git repository (~/.config/JetBrains/PhpStorm*/templates/). File → Manage IDE Settings → Export Settings → select Live Templates.
Hyva & Magento
Templates for ViewModels, plugins, Alpine.js components, and $hyvaCsp->registerInlineScript() calls. escHtml postfix for safe template output.