StructuralPHPverifiedVerified
Flyweight Pattern in PHP
Minimizes memory usage by sharing fine-grained objects that represent repeated data, storing intrinsic state once and passing extrinsic state at call time.
How to Implement the Flyweight Pattern in PHP
1Step 1: Define the flyweight that holds shared (intrinsic) state
class TreeType
{
public function __construct(
public readonly string $name,
public readonly string $color,
public readonly string $texture,
) {}
public function draw(float $x, float $y): string
{
return "Drawing {$this->name} at ({$x},{$y}) color={$this->color}";
}
}2Step 2: Implement the flyweight factory
class TreeFactory
{
/** @var array<string, TreeType> */
private static array $types = [];
public static function getTreeType(string $name, string $color, string $texture): TreeType
{
$key = "{$name}_{$color}_{$texture}";
if (!isset(self::$types[$key])) {
self::$types[$key] = new TreeType($name, $color, $texture);
echo "Created new TreeType: {$key}\n";
}
return self::$types[$key];
}
public static function getTypeCount(): int
{
return count(self::$types);
}
}3Step 3: Context holds unique (extrinsic) state
class Tree
{
public function __construct(
private readonly float $x,
private readonly float $y,
private readonly TreeType $type,
) {}
public function draw(): string
{
return $this->type->draw($this->x, $this->y);
}
}
// Usage — many trees share few TreeType instances
$forest = [];
for ($i = 0; $i < 1000; $i++) {
$type = TreeFactory::getTreeType('Oak', 'green', 'rough');
$forest[] = new Tree(rand(0, 500), rand(0, 500), $type);
}
echo "Trees: " . count($forest) . ", Types: " . TreeFactory::getTypeCount(); // 1000 trees, 1 type<?php
declare(strict_types=1);
// [step] Define the flyweight interface for text rendering
final readonly class CharacterStyle
{
public function __construct(
public string $fontFamily,
public int $fontSize,
public string $color,
public bool $bold,
public bool $italic,
) {}
public function getKey(): string
{
$flags = ($this->bold ? 'B' : '') . ($this->italic ? 'I' : '');
return "{$this->fontFamily}_{$this->fontSize}_{$this->color}_{$flags}";
}
}
// [step] Implement the flyweight factory with memory tracking
final class CharacterStyleFactory
{
/** @var array<string, CharacterStyle> */
private array $styles = [];
private int $cacheHits = 0;
private int $cacheMisses = 0;
public function getStyle(
string $fontFamily,
int $fontSize,
string $color,
bool $bold = false,
bool $italic = false,
): CharacterStyle {
$style = new CharacterStyle($fontFamily, $fontSize, $color, $bold, $italic);
$key = $style->getKey();
if (isset($this->styles[$key])) {
$this->cacheHits++;
return $this->styles[$key];
}
$this->cacheMisses++;
$this->styles[$key] = $style;
return $style;
}
public function getStyleCount(): int { return count($this->styles); }
public function getCacheHits(): int { return $this->cacheHits; }
public function getCacheMisses(): int { return $this->cacheMisses; }
/** @return array{styles: int, hits: int, misses: int, hitRate: float} */
public function getStats(): array
{
$total = $this->cacheHits + $this->cacheMisses;
return [
'styles' => count($this->styles),
'hits' => $this->cacheHits,
'misses' => $this->cacheMisses,
'hitRate' => $total > 0 ? $this->cacheHits / $total : 0,
];
}
}
// [step] Context that combines flyweight with extrinsic state
final class StyledCharacter
{
public function __construct(
public readonly string $char,
public readonly int $position,
public readonly CharacterStyle $style,
) {}
public function render(): string
{
$weight = $this->style->bold ? 'bold' : 'normal';
$fontStyle = $this->style->italic ? 'italic' : 'normal';
return sprintf(
'<span style="font-family:%s;font-size:%dpx;color:%s;font-weight:%s;font-style:%s">%s</span>',
$this->style->fontFamily,
$this->style->fontSize,
$this->style->color,
$weight,
$fontStyle,
htmlspecialchars($this->char),
);
}
}
// [step] Document that uses flyweight characters
final class RichTextDocument
{
/** @var StyledCharacter[] */
private array $characters = [];
public function __construct(
private readonly CharacterStyleFactory $styleFactory,
) {}
public function addText(string $text, string $font, int $size, string $color, bool $bold = false): void
{
$style = $this->styleFactory->getStyle($font, $size, $color, $bold);
$offset = count($this->characters);
for ($i = 0; $i < strlen($text); $i++) {
$this->characters[] = new StyledCharacter($text[$i], $offset + $i, $style);
}
}
public function render(): string
{
return implode('', array_map(fn(StyledCharacter $c) => $c->render(), $this->characters));
}
public function getCharacterCount(): int { return count($this->characters); }
/** @return array{styles: int, hits: int, misses: int, hitRate: float} */
public function getMemoryStats(): array
{
return $this->styleFactory->getStats();
}
}Flyweight Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Flyweight Pattern in the Real World
“A book publisher doesn’t print a separate metal typeface block for every letter ‘e’ on every page. Instead, one block for ‘e’ (intrinsic state) is reused in every position, with the printer supplying the ink color and position (extrinsic state) each time it is stamped. Thousands of impressions share one piece of metal.”