BehavioralPHPverifiedVerified
Template Method Pattern in PHP
Defines the skeleton of an algorithm in a base class, deferring certain steps to subclasses without changing the algorithm's overall structure.
How to Implement the Template Method Pattern in PHP
1Step 1: Define the abstract class with the template method
abstract class DataProcessor
{
// Template method — defines the algorithm skeleton
final public function process(string $source): string
{
$raw = $this->readData($source);
$parsed = $this->parseData($raw);
$result = $this->transformData($parsed);
return $this->formatOutput($result);
}
// Steps to be implemented by subclasses
abstract protected function readData(string $source): string;
abstract protected function parseData(string $raw): array;
// Hook with default implementation
protected function transformData(array $data): array
{
return $data; // No transformation by default
}
protected function formatOutput(array $data): string
{
return json_encode($data);
}
}2Step 2: Implement concrete subclasses
class CsvProcessor extends DataProcessor
{
protected function readData(string $source): string
{
return "name,age\nAlice,30\nBob,25";
}
protected function parseData(string $raw): array
{
$lines = explode("\n", $raw);
$headers = str_getcsv(array_shift($lines));
return array_map(
fn(string $line) => array_combine($headers, str_getcsv($line)),
$lines,
);
}
}
class JsonProcessor extends DataProcessor
{
protected function readData(string $source): string
{
return '[{"name":"Alice","age":30}]';
}
protected function parseData(string $raw): array
{
return json_decode($raw, true);
}
}
// Usage
$csv = new CsvProcessor();
echo $csv->process('data.csv');<?php
declare(strict_types=1);
// [step] Define the abstract report generator with template method
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
public function error(string $message, array $context = []): void;
}
final readonly class ReportResult
{
public function __construct(
public string $content,
public string $format,
public int $recordCount,
public float $generationTimeMs,
) {}
}
abstract class ReportGenerator
{
public function __construct(
protected readonly LoggerInterface $logger,
) {}
// [step] Template method — defines the report generation algorithm
final public function generate(string $dataSource): ReportResult
{
$start = hrtime(true);
$this->logger->info('Starting report generation', ['source' => $dataSource]);
$data = $this->fetchData($dataSource);
$filtered = $this->filterData($data);
$sorted = $this->sortData($filtered);
$formatted = $this->formatReport($sorted);
$this->onComplete($formatted);
$durationMs = (hrtime(true) - $start) / 1e6;
return new ReportResult(
content: $formatted,
format: $this->getFormat(),
recordCount: count($sorted),
generationTimeMs: $durationMs,
);
}
// Abstract steps subclasses must implement
abstract protected function fetchData(string $source): array;
abstract protected function formatReport(array $data): string;
abstract protected function getFormat(): string;
// Hooks with default implementations
protected function filterData(array $data): array
{
return $data; // No filtering by default
}
protected function sortData(array $data): array
{
return $data; // No sorting by default
}
protected function onComplete(string $report): void
{
// Override for post-processing (e.g., caching, notifications)
}
}
// [step] Concrete report generators
final class HtmlReportGenerator extends ReportGenerator
{
protected function fetchData(string $source): array
{
return [['name' => 'Widget A', 'sales' => 150], ['name' => 'Widget B', 'sales' => 320]];
}
protected function formatReport(array $data): string
{
$rows = array_map(
fn(array $row) => sprintf('<tr><td>%s</td><td>%d</td></tr>', $row['name'], $row['sales']),
$data,
);
return '<table><tr><th>Name</th><th>Sales</th></tr>' . implode('', $rows) . '</table>';
}
protected function getFormat(): string { return 'html'; }
protected function sortData(array $data): array
{
usort($data, fn(array $a, array $b) => $b['sales'] <=> $a['sales']);
return $data;
}
}
final class CsvReportGenerator extends ReportGenerator
{
protected function fetchData(string $source): array
{
return [['name' => 'Widget A', 'sales' => 150], ['name' => 'Widget B', 'sales' => 320]];
}
protected function formatReport(array $data): string
{
$lines = ['name,sales'];
foreach ($data as $row) {
$lines[] = "{$row['name']},{$row['sales']}";
}
return implode("\n", $lines);
}
protected function getFormat(): string { return 'csv'; }
}Template Method Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Template Method Pattern in the Real World
“Consider a recipe for baking bread. The overall process—mix, knead, let rise, bake, cool—is fixed. But the specific flour blend, kneading technique, and baking temperature are decisions left to the baker. The cookbook provides the invariant sequence; individual bakers customize the steps that can vary without disrupting the overall process.”