Live Templates and Postfix Templates for PHP Teams in PhpStorm
AI generated
IDE
{ }
PhpStorm · Live Templates · Postfix · PHP 8.4 · Magento
Live Templates and Postfix Templates
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.

16 min read Live Templates · Postfix · PHP 8.4 · Magento · Hyva PhpStorm 2024.x · 2025.x

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.

10. FAQ: Live Templates and Postfix Templates for PHP teams

1Difference between Live Templates and Postfix Templates?
Live Templates: abbreviation + Tab before the expression. Postfix Templates: expression + .abbreviation to wrap it. They complement each other.
2Where does PhpStorm store Live Templates?
Linux: ~/.config/JetBrains/PhpStorm[version]/templates/ as XML. Check these files directly into the Git repo for team sharing.
3Which expressions for template variables?
phpClassName(), fileNameWithoutExtension(), phpNamespace(), date(format), clipboard(). More than 20 built-in expressions available.
4Create custom Postfix Templates?
Settings → Editor → General → Postfix Completion → +. Use $EXPR$ as the placeholder for the preceding expression.
5Sharing Live Templates with your team?
File → Manage IDE Settings → Export Settings → Live Templates. Check the ZIP into the Git repository. Import via File → Manage IDE Settings → Import Settings.
6What does "Skip if defined" mean?
The Tab cursor skips this variable if the expression already provides a value. Prevents unnecessary cursor stops at auto-filled variables.
7Prevent a template from appearing in the wrong context?
Configure Applicable In as narrowly as possible: PHP → Statement, Class Member, Expression. Broad contexts clutter the autocomplete list.
8Default Postfix Templates for PHP?
.var, .return, .not, .null, .notnull, .cast, .foreach, and more. All visible under Settings → Editor → General → Postfix Completion.
9Live Template vs. File Template?
File Templates for new files (Settings → Editor → File and Code Templates). Live Templates for code within existing files.
10escHtml postfix in phtml files?
Create a custom Postfix Template with $block->escapeHtml($EXPR$). Use PHP → Expression as the context. phtml is treated by PhpStorm as PHP.