PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Template Method – algorithm skeleton, hooks, abstract vs hook, comparison with Strategy

by Henryk Tews / Tuesday, 08 November 2022 / Published in Wzorce projektowe

Template Method defines the skeleton of an algorithm in a base class and lets subclasses fill in specific steps. It is the backbone of inheritance-based extensibility – and one of the most widely used patterns in frameworks like Magento 2, Symfony and Laravel. I show the classic GoF implementation, the difference between abstract steps and hooks, and a comparison with Strategy.

Classic Template Method

<?php

declare(strict_types=1);

// Base class defines the algorithm skeleton
abstract class DataImporter
{
    // Template method - defines the steps, cannot be overridden
    final public function import(string $source): array
    {
        $rawData    = $this->readData($source);      // step 1: read
        $validated  = $this->validateData($rawData); // step 2: validate
        $transformed = $this->transformData($validated); // step 3: transform
        $this->saveData($transformed);               // step 4: save
        $this->cleanup();                            // step 5: cleanup (hook)

        return $transformed;
    }

    // Abstract step - MUST be implemented by subclasses
    abstract protected function readData(string $source): array;
    abstract protected function validateData(array $data): array;
    abstract protected function transformData(array $data): array;
    abstract protected function saveData(array $data): void;

    // Hook - MAY be overridden, has a default implementation
    protected function cleanup(): void
    {
        // Default: do nothing
    }
}

// Concrete implementation for CSV
class CsvProductImporter extends DataImporter
{
    protected function readData(string $source): array
    {
        $rows = [];
        $file = fopen($source, 'r');
        while (($row = fgetcsv($file)) !== false) {
            $rows[] = $row;
        }
        fclose($file);
        return array_slice($rows, 1); // skip header
    }

    protected function validateData(array $data): array
    {
        return array_filter($data, fn($row) =>
            !empty($row[0]) && is_numeric($row[2]) // sku and price required
        );
    }

    protected function transformData(array $data): array
    {
        return array_map(fn($row) => [
            'sku'    => trim($row[0]),
            'name'   => trim($row[1]),
            'price'  => (float) $row[2],
            'active' => (bool) ($row[3] ?? 1),
        ], $data);
    }

    protected function saveData(array $data): void
    {
        foreach ($data as $item) {
            // save to Magento via ProductRepository
        }
    }

    // Override the hook to clean up temp files
    protected function cleanup(): void
    {
        unlink($this->tempFile);
    }
}

// JSON importer only needs to implement the read step differently
class JsonProductImporter extends DataImporter
{
    protected function readData(string $source): array
    {
        return json_decode(file_get_contents($source), true, 512, JSON_THROW_ON_ERROR);
    }

    protected function validateData(array $data): array
    {
        return array_filter($data, fn($item) => isset($item['sku'], $item['price']));
    }

    protected function transformData(array $data): array { return $data; }
    protected function saveData(array $data): void { /* ... */ }
}

// Usage - same call regardless of format
$csvImporter  = new CsvProductImporter();
$jsonImporter = new JsonProductImporter();

$csvImporter->import('/tmp/products.csv');
$jsonImporter->import('/api/products.json');

Abstract steps vs Hooks

Type In code Required? Default? Use when
Abstract step abstract protected function Yes No Step is always needed but implementation varies
Hook protected function with empty or default body No Yes Optional step that some subclasses may need

Template Method in Magento 2

<?php

// AbstractModel defines _beforeSave and _afterSave hooks
class MyModel extends \Magento\Framework\Model\AbstractModel
{
    protected function _construct(): void
    {
        $this->_init(\Vendor\Module\Model\ResourceModel\MyModel::class);
    }

    // Override hook - runs before every save
    protected function _beforeSave(): static
    {
        if (empty($this->getCreatedAt())) {
            $this->setCreatedAt(date('Y-m-d H:i:s'));
        }
        $this->setUpdatedAt(date('Y-m-d H:i:s'));
        return parent::_beforeSave();
    }

    // Override hook - runs after every save
    protected function _afterSave(): static
    {
        // Clear related cache after save
        return parent::_afterSave();
    }
}

// AbstractBlock defines _toHtml() template method
class MyBlock extends \Magento\Framework\View\Element\Template
{
    // Hook: override to prepare data before template rendering
    protected function _prepareLayout(): static
    {
        $this->setData('products', $this->loadProducts());
        return parent::_prepareLayout();
    }
}

Template Method vs Strategy

Aspect Template Method Strategy
Mechanism Inheritance Composition
Algorithm steps Fixed skeleton, variable steps Entire algorithm interchangeable
Runtime swap? No – type fixed at object creation Yes – strategy can be changed
Coupling Tight (subclass knows parent) Loose (context knows interface)
When to use Algorithm steps vary but structure is stable Algorithms are completely independent

Summary

Template Method is one of the simplest and most broadly useful patterns. It is the right choice when you have a stable algorithm structure with variable steps – data importers, report generators, export pipelines, model lifecycle hooks. Prefer Strategy when the entire algorithm varies or when you need runtime swapping. In Magento 2, AbstractModel, AbstractBlock and AbstractCarrier all use Template Method – knowing the pattern explains exactly how and where to extend them.

About Henryk Tews

What you can read next

Interpreter pattern – own discount rule grammar, parser, expression tree
Singleton and Builder in PHP – creational patterns
State pattern – order state machine, serialisation, comparison with Strategy

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}