← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Named Constructor Pattern

PHP PHP 5.0+ Intermediate
debt(d7/e3/b3/t5)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). PHPStan is listed as a detection tool but it cannot automatically flag that a class is missing named constructors or that a public constructor should be private — these are design-level decisions. A reviewer must inspect the API surface to notice that both `new Money(500, 'cents')` and `Money::fromCents(500)` are available, or that construction intent is ambiguous. The common mistake of not making the constructor private goes completely undetected by automated analysis.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix describes adding static named constructor methods and making the regular constructor private — this is a small, localised refactor within one class file. Call sites using `new` must be updated to the named constructor, touching multiple call sites but with a straightforward search-and-replace pattern. Not a single-line patch (e1) because constructor visibility change and call-site updates are required, but not a multi-file architectural change.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3). The pattern applies at the individual class level — each value object or domain entity that adopts named constructors carries its own small tax (private constructor, static methods, validation logic). The rest of the codebase is mostly unaffected unless the class is widely used. It does not impose a cross-cutting constraint, but callers must use the named constructor API rather than `new`, which is a localised constraint.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap' (t5). The misconception field explicitly states the trap: developers think named constructors are 'just static methods' and miss that they should enforce all construction through validated paths, can return subtypes, and replace the public constructor entirely. The common mistake of not making the constructor private confirms this is a documented, frequently encountered gotcha — developers add the static method but leave the direct `new` path open, negating the intent-clarity and validation enforcement benefits.

About DEBT scoring →

Also Known As

static factory method factory method named constructor

TL;DR

Static factory methods with descriptive names that replace overloaded constructors — making object creation intent clear when multiple creation paths exist.

Explanation

PHP constructors have a single name (__construct) and cannot be overloaded. Named constructors are static methods that communicate intent: Money::fromCents(500), Money::fromDollars(5.00), DateRange::lastMonth(), User::createGuest(). They can return different subtypes, enforce invariants, or accept different input formats. Combined with a private constructor, they become the only way to instantiate the class — enforcing all construction passes through validated paths.

Common Misconception

Named constructors are just static methods — they communicate intent by name, enforce all construction through validated paths, and can return different subtypes, none of which constructors support.

Why It Matters

new Money(500, 'cents') vs Money::fromCents(500) — the named constructor makes the unit explicit and prevents the ambiguity that causes subtle currency calculation bugs.

Common Mistakes

  • Not making the constructor private when named constructors are the intended API — both paths remain available.
  • Named constructors that accept the same parameters as the constructor — adds verbosity without clarity.
  • Omitting validation in named constructors — they are the perfect place to enforce class invariants.
  • Using static methods for everything instead of identifying which cases genuinely need named constructors.

Code Examples

✗ Vulnerable
// Ambiguous constructor — is 500 cents or dollars?
class Money {
    public function __construct(private int $amount, private string $currency) {}
}
new Money(500, 'USD'); // $500 or 500 cents? Who knows
✓ Fixed
// Named constructors — intent is explicit:
class Money {
    private function __construct(
        private readonly int $cents,
        private readonly string $currency,
    ) {}

    public static function fromCents(int $cents, string $currency): self {
        if ($cents < 0) throw new InvalidArgumentException('Amount cannot be negative');
        return new self($cents, $currency);
    }

    public static function fromDollars(float $dollars, string $currency): self {
        return new self((int) round($dollars * 100), $currency);
    }

    public static function zero(string $currency): self {
        return new self(0, $currency);
    }
}

Added 15 Mar 2026
Edited 22 Mar 2026
Views 45
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 0 pings S 0 pings S 0 pings M 0 pings T 2 pings W 1 ping T 1 ping F 0 pings S 0 pings S 0 pings M 0 pings T 1 ping W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W
No pings yet today
SEMrush 1
Amazonbot 9 Google 5 Ahrefs 4 Perplexity 3 Bing 3 Scrapy 3 Unknown AI 2 ChatGPT 2 Majestic 1 Claude 1 Meta AI 1 SEMrush 1
crawler 32 crawler_json 3
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: Low
⚡ Quick Fix
Add static named constructors (Money::fromCents(500), Email::fromString('user@example.com')) to make object creation intent-clear and allow domain validation in the constructor
📦 Applies To
PHP 5.0+ web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
new Money(500, 'cents') — what does 500 mean? Named constructor makes it Money::fromCents(500)
Auto-detectable: ✗ No phpstan
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: High ✗ Manual fix Fix: Low Context: Class


✓ schema.org compliant