Anonyme Klassen in PHP: Metaprogrammierung ohne benannte Klassendeklaration
AI generated
<?php
8.4
PHP · Anonyme Klassen · Metaprogrammierung
Anonyme Klassen in PHP
Metaprogrammierung ohne benannte Klassendeklaration

Anonyme Klassen erlauben es, mit new class() eine vollwertige Klasse direkt an der Verwendungsstelle zu definieren, ohne einen globalen Namen zu vergeben. Wer sie richtig einsetzt, ersetzt damit Mocking-Bibliotheken bei Tests, vermeidet unnötige Klassennamen für One-off-Implementierungen und schreibt kompakteren, dennoch typsicheren Code.

17 Min. Lesezeit new class() · Interfaces · Test Doubles PHP 7.0+ · 8.4

1. Was anonyme Klassen sind und wie sie sich von benannten unterscheiden

Eine anonyme Klasse ist eine Klassendeklaration ohne eigenen Namen, die direkt an der Stelle ihrer Verwendung mit new class() {...} definiert und sofort instanziiert wird. Seit PHP 7.0 ist dieses Sprachfeature Teil des Kerns und funktioniert syntaktisch identisch zu einer normalen Klasse: Properties, Methoden, Konstruktoren, implementierte Interfaces und sogar geerbte Elternklassen sind alle erlaubt, nur der Bezeichner nach dem Schlüsselwort class entfällt.

Der Sinn dieses Features liegt in der Reduktion von Boilerplate für Fälle, in denen eine Klasse nur an genau einer Stelle im Code gebraucht wird und ein eigener, global sichtbarer Klassenname keinen Mehrwert bietet, sondern eher Rauschen erzeugt. Statt für einen einmaligen Anwendungsfall eine benannte Klasse in einer eigenen Datei anzulegen, die anschließend niemand außer der einen Aufrufstelle jemals wieder verwendet, bündelt eine anonyme Klasse Definition und Verwendung an einer einzigen, leicht nachvollziehbaren Stelle im Code.

Wichtig für die Einordnung: Anonyme Klassen sind kein Ersatz für reguläre Klassen im Allgemeinen. Sie sind ein gezieltes Werkzeug für lokale, oft testbezogene oder konfigurationsgetriebene Anwendungsfälle. Wer eine Klasse an mehreren Stellen im Projekt wiederverwenden will, sollte immer eine benannte Klasse bevorzugen, weil anonyme Klassen keinen stabilen, im Code referenzierbaren Namen besitzen.

2. Syntax und Grundlagen: new class() {...} im Detail

Die Basissyntax einer anonymen Klasse ist new class { ... }, wobei die runden Klammern für Konstruktor-Argumente optional entfallen können, wenn kein Konstruktor benötigt wird. Innerhalb der geschweiften Klammern gilt exakt dieselbe Syntax wie bei einer benannten Klasse: Properties mit Sichtbarkeitsmodifikatoren, typisierte Methoden, Konstanten und sogar verschachtelte anonyme Klassen als Rückgabewerte einer Methode sind erlaubt.

Ein häufiger Stolperstein zu Beginn: Eine anonyme Klasse kann nicht direkt in einer Konstante oder in einer Property-Default-Deklaration verwendet werden, weil PHP an diesen Stellen nur konstante Ausdrücke zulässt und new class() ein Laufzeit-Ausdruck ist. Anonyme Klassen sind also strikt an Ausführungskontexte gebunden, in denen beliebige Ausdrücke ausgewertet werden dürfen, etwa innerhalb einer Funktion, einer Methode oder direkt als Rückgabewert.


<?php

declare(strict_types=1);

interface PriceFormatter
{
    public function format(float $amount): string;
}

function makeEuroFormatter(): PriceFormatter
{
    // The anonymous class is defined and instantiated in a single expression
    return new class implements PriceFormatter {
        public function format(float $amount): string
        {
            return number_format($amount, 2, ',', '.') . ' EUR';
        }
    };
}

$formatter = makeEuroFormatter();
echo $formatter->format(1299.9) . PHP_EOL; // 1.299,90 EUR

3. Konstruktor-Argumente und Interfaces bei anonymen Klassen

Anonyme Klassen unterstützen Konstruktoren mit beliebigen Parametern genau wie benannte Klassen, einschließlich Constructor Property Promotion. Die Argumente werden direkt in den runden Klammern nach new class(...) übergeben, was die Definition und Instanziierung noch enger verzahnt: Der Aufrufer sieht sofort, mit welchen konkreten Werten die Klasse arbeitet, ohne an eine andere Codestelle springen zu müssen.

Ebenso können anonyme Klassen ein oder mehrere Interfaces implementieren, was in der Praxis der wichtigste Anwendungsfall ist. Der Rückgabetyp einer Funktion bleibt dabei stabil und typsicher, weil er sich auf das implementierte Interface bezieht, während die konkrete Implementierung als anonyme Klasse verborgen bleibt und niemals von außen namentlich referenziert werden muss. Dieses Muster ist eine schlanke Alternative zum klassischen Strategy Pattern, wenn nur eine einzige, lokale Implementierungsvariante gebraucht wird.


<?php

declare(strict_types=1);

interface DiscountPolicy
{
    public function apply(float $price): float;
}

final class Cart
{
    public function __construct(
        private readonly DiscountPolicy $discountPolicy,
    ) {
    }

    public function totalAfterDiscount(float $price): float
    {
        return $this->discountPolicy->apply($price);
    }
}

// Constructor arguments passed directly into the anonymous class
$cart = new Cart(new class (0.15) implements DiscountPolicy {
    public function __construct(private readonly float $rate)
    {
    }

    public function apply(float $price): float
    {
        return $price * (1 - $this->rate);
    }
});

echo $cart->totalAfterDiscount(200.0) . PHP_EOL; // 170

4. Praxisbeispiel: Test Doubles ohne Mocking-Framework

Der wohl häufigste praktische Einsatzort für anonyme Klassen ist das Schreiben von Test Doubles in Unit Tests, ohne dafür eine schwergewichtige Mocking-Bibliothek zu benötigen. Statt einer generierten Mock-Instanz mit konfigurierten Erwartungen liefert eine anonyme Klasse eine echte, minimale Implementierung eines Interfaces, deren Verhalten exakt auf den jeweiligen Testfall zugeschnitten ist und die im Test selbst vollständig lesbar bleibt.

Diese Technik ist besonders robust gegenüber Refactorings der Mocking-Bibliothek, weil kein zusätzliches Test-Framework-API im Spiel ist. Der Test verlässt sich lediglich auf reguläre PHP-Sprachfeatures: Interface, anonyme Klasse, Konstruktor-Argumente. Für einfache Fake-Implementierungen, etwa ein In-Memory-Repository, das eine Datenbank ersetzt, ist dieser Ansatz oft lesbarer als eine Mock-Konfiguration mit mehreren verketteten Erwartungsaufrufen.


<?php

declare(strict_types=1);

interface OrderRepository
{
    public function findById(int $id): ?Order;
}

final class Order
{
    public function __construct(public readonly int $id, public readonly float $total)
    {
    }
}

final class OrderSummaryTest extends PHPUnit\Framework\TestCase
{
    public function testSummaryFormatsTotal(): void
    {
        // Anonymous class as a fake repository — no mocking library required
        $repository = new class implements OrderRepository {
            public function findById(int $id): ?Order
            {
                return $id === 42 ? new Order(42, 199.5) : null;
            }
        };

        $service = new OrderSummaryService($repository);

        self::assertSame('Order #42: 199.50 EUR', $service->summarize(42));
    }
}

5. Praxisbeispiel: One-off Event-Listener und Callback-Objekte

Neben Tests eignen sich anonyme Klassen für Event-Listener oder Callback-Objekte, die nur an genau einer Stelle registriert werden und komplexer sind als eine einfache Closure, etwa weil sie internen Zustand über mehrere Methoden hinweg teilen müssen. Wo eine Closure nur eine einzelne Funktion kapselt, kann eine anonyme Klasse mehrere zusammengehörige Methoden und Properties bündeln, ohne dass dafür eine eigene, global sichtbare Klasse nötig wäre.

Ein typisches Beispiel ist ein Event-Listener, der sowohl auf ein Ereignis reagieren als auch anschließend einen internen Zähler oder gesammelte Ergebnisse bereitstellen muss. Eine Closure könnte das nur über use (&$state) und externe Referenzvariablen abbilden, was schnell unübersichtlich wird. Die anonyme Klasse macht denselben Zustand explizit als Property sichtbar und bleibt dabei vollständig lokal an der Registrierungsstelle.


<?php

declare(strict_types=1);

interface EventListener
{
    public function handle(string $eventName, array $payload): void;
}

final class EventDispatcher
{
    /** @var EventListener[] */
    private array $listeners = [];

    public function subscribe(EventListener $listener): void
    {
        $this->listeners[] = $listener;
    }

    public function dispatch(string $eventName, array $payload): void
    {
        foreach ($this->listeners as $listener) {
            $listener->handle($eventName, $payload);
        }
    }
}

$dispatcher = new EventDispatcher();

// Anonymous class bundles state (the counter) and behavior in one place
$dispatcher->subscribe(new class implements EventListener {
    private int $orderCount = 0;

    public function handle(string $eventName, array $payload): void
    {
        if ($eventName === 'order.placed') {
            $this->orderCount++;
            echo "Orders placed so far: {$this->orderCount}" . PHP_EOL;
        }
    }
});

6. Anonyme Klassen und Vererbung: extends im Detail

Anonyme Klassen können ebenso eine bestehende benannte Klasse mit extends erweitern, um deren Verhalten punktuell zu überschreiben, ohne für diese einmalige Anpassung eine eigene benannte Unterklasse anzulegen. Das ist besonders in Tests nützlich, wenn eine einzelne Methode einer Elternklasse für einen spezifischen Testfall ein abweichendes Verhalten zeigen soll, während der Rest der Klasse unverändert bleibt.

Bei der Kombination aus extends und implements gelten dieselben Regeln wie bei benannten Klassen: Eine anonyme Klasse kann von genau einer Klasse erben, dabei aber beliebig viele Interfaces zusätzlich implementieren. Abstrakte Methoden der Elternklasse müssen dabei ebenso vollständig implementiert werden, PHP macht hier keine Ausnahme für den fehlenden Klassennamen.


<?php

declare(strict_types=1);

class HttpClient
{
    public function get(string $url): string
    {
        // Real implementation would perform an actual HTTP request
        return file_get_contents($url);
    }
}

function fakeHttpClientReturning(string $body): HttpClient
{
    // Extend a concrete class and override a single method for a test
    return new class ($body) extends HttpClient {
        public function __construct(private readonly string $fixedBody)
        {
        }

        public function get(string $url): string
        {
            return $this->fixedBody;
        }
    };
}

7. Reflection auf anonyme Klassen: Namen, Caching und Identität

Obwohl anonyme Klassen keinen im Quellcode sichtbaren Namen tragen, weist PHP intern trotzdem jeder anonymen Klasse einen generierten Klassennamen zu, der über get_class() oder ReflectionObject sichtbar wird. Dieser Name hat typischerweise die Form class@anonymous gefolgt von Dateipfad und Zeilennummer, was in Stacktraces und Fehlermeldungen die Rückverfolgung zur Definitionsstelle erlaubt, selbst ohne echten Bezeichner.

Für Reflection-basierte Werkzeuge ist wichtig zu wissen, dass PHP dieselbe anonyme Klassendefinition bei mehrfacher Ausführung desselben Codeabschnitts, etwa in einer Schleife, wiederverwendet und nicht bei jedem Durchlauf eine neue Klasse deklariert, solange sich die Definition im Quellcode nicht ändert. Zwei Instanzen aus demselben new class()-Ausdruck teilen sich also dieselbe zugrunde liegende Klasse, was get_class($a) === get_class($b) bestätigt, auch wenn beide Objekte unterschiedliche Zustände tragen.


<?php

declare(strict_types=1);

function makeCounter(): object
{
    return new class {
        private int $value = 0;

        public function increment(): int
        {
            return ++$this->value;
        }
    };
}

$first = makeCounter();
$second = makeCounter();

// Both instances share the same generated class name
echo get_class($first) . PHP_EOL; // class@anonymous/path/to/file.php:0x...
var_dump(get_class($first) === get_class($second)); // bool(true)

$reflection = new ReflectionObject($first);
echo $reflection->isAnonymous() ? 'anonymous' : 'named'; // anonymous

8. Performance und Speicher: wie PHP anonyme Klassen intern behandelt

PHP kompiliert eine anonyme Klasse beim ersten Erreichen der entsprechenden Codezeile und legt sie danach genauso im internen Klassen-Cache ab wie eine benannte Klasse. Wiederholte Ausführung derselben new class()-Zeile, etwa in einer Schleife mit tausend Iterationen, erzeugt also keine tausend unterschiedlichen Klassendefinitionen, sondern immer nur neue Instanzen derselben einen kompilierten Klasse. Der Overhead einer anonymen Klasse gegenüber einer benannten Klasse ist damit zur Laufzeit praktisch nicht messbar.

Ein Punkt, der in der Praxis gelegentlich übersehen wird: OPcache behandelt anonyme Klassen wie jede andere Klassendefinition und cacht den kompilierten Bytecode zwischen Requests, sofern die Datei, in der die anonyme Klasse steht, nicht neu geladen werden muss. Es gibt also keinen Performance-Grund, anonyme Klassen zu meiden. Der einzige relevante Nachteil ist rein struktureller Natur: fehlende Wiederverwendbarkeit über die Definitionsstelle hinaus, nicht Laufzeit-Overhead.

9. Anonyme Klassen im Vergleich zu Closures und benannten Klassen

Die Wahl zwischen anonymer Klasse, Closure und benannter Klasse hängt vom konkreten Anwendungsfall ab. Die folgende Tabelle zeigt, wann welche Variante die bessere Wahl ist.

Anwendungsfall Empfohlenes Werkzeug Grund
Einzelne Funktion als Wert übergeben Closure Kein Interface, kein mehrfacher Zustand nötig
Interface-Implementierung nur an einer Stelle Anonyme Klasse Typsicher, keine unnötige globale Klasse
Test Double mit mehreren Methoden Anonyme Klasse Ersetzt Mocking-Framework, bleibt lesbar
Wiederverwendung an mehreren Stellen Benannte Klasse Referenzierbarer Name für mehrfache Nutzung
Zustand plus mehrere zusammengehörige Methoden, lokal Anonyme Klasse Übersichtlicher als Closure mit use(&$state)

Der praktische Unterschied zwischen Closure und anonymer Klasse liegt in der Anzahl der öffentlichen Einstiegspunkte: Eine Closure ist immer genau eine aufrufbare Funktion, eine anonyme Klasse kann beliebig viele Methoden und mehrere Interfaces gleichzeitig anbieten. Sobald mehr als eine Methode oder ein Interface-Vertrag mit mehreren Methoden im Spiel ist, ist die anonyme Klasse fast immer die klarere Lösung.

Mironsoft

PHP-Testarchitektur und schlanke, wartbare Codebasen

Testcode und Boilerplate reduzieren?

Wir bauen Testsuiten und Anwendungscode, die moderne PHP-Sprachfeatures wie anonyme Klassen gezielt einsetzen, um Mocking-Overhead und unnötige Klassenwucherung zu vermeiden.

Test-Refactoring

Mocking-Frameworks durch schlanke Test Doubles ersetzen

Code-Review

Unnötige One-off-Klassen erkennen und konsolidieren

PHP-Schulung

Moderne Sprachfeatures im Team praxisnah etablieren

10. Zusammenfassung

Anonyme Klassen in PHP erlauben es, mit new class() eine vollwertige Klasse direkt an der Verwendungsstelle zu definieren und sofort zu instanziieren, inklusive Konstruktor-Argumenten, implementierten Interfaces und Vererbung von einer bestehenden Klasse. Der größte praktische Nutzen liegt in Test Doubles ohne Mocking-Bibliothek und in lokalen Event-Listenern oder Strategie-Implementierungen, die nur an genau einer Stelle im Code gebraucht werden.

Intern behandelt PHP anonyme Klassen wie jede andere Klasse: Sie werden einmal kompiliert, in OPcache gecacht, und mehrfache Ausführung derselben Codezeile erzeugt keine neuen Klassendefinitionen, sondern nur neue Instanzen. Der einzige echte Nachteil ist struktureller Natur: fehlende Wiederverwendbarkeit über die Definitionsstelle hinaus. Wer eine Implementierung an mehreren Stellen braucht, sollte immer eine benannte Klasse wählen.

Anonyme Klassen in PHP — Das Wichtigste auf einen Blick

Syntax

new class(...) implements X extends Y {...} — identisch zur benannten Klasse, nur ohne Namen.

Haupteinsatz

Test Doubles ohne Mocking-Framework, lokale Event-Listener und One-off Strategie-Implementierungen.

Performance

Einmal kompiliert, in OPcache gecacht, kein messbarer Overhead gegenüber benannten Klassen.

Grenze

Keine Wiederverwendbarkeit über die Definitionsstelle hinaus, dann benannte Klasse verwenden.

11. FAQ: Anonyme Klassen in PHP

1Was ist eine anonyme Klasse?
Eine Klassendeklaration ohne Namen, direkt mit new class() definiert und instanziiert, seit PHP 7.0 mit denselben Features wie eine benannte Klasse.
2Konstruktor möglich?
Ja, mit beliebigen Parametern und Property Promotion, Argumente direkt in den Klammern nach new class(...).
3Interface implementieren?
Ja, mit implements, auch mehrere gleichzeitig. Häufigster Anwendungsfall für Test Doubles.
4Vererbung möglich?
Ja, mit extends kann eine bestehende Klasse erweitert werden, nützlich für Tests mit abweichendem Verhalten einer Methode.
5Interner Name?
Form class@anonymous mit Dateipfad und Zeilennummer, sichtbar über get_class() oder ReflectionObject::isAnonymous().
6Langsamer als benannte Klasse?
Nein, wird identisch kompiliert und gecacht, kein messbarer Overhead.
7Closure statt anonymer Klasse?
Bei nur einer Funktion ohne Interface. Bei mehreren Methoden oder Interface-Vertrag ist die anonyme Klasse klarer.
8Statt Mocking-Framework?
Ja, häufigster Einsatzort: echte, minimale Interface-Implementierung ohne zusätzliches Mocking-API.
9Gleiche Klasse bei mehreren Instanzen?
Ja, aus derselben Codezeile teilen sich alle Instanzen dieselbe generierte Klasse.
10Größte Grenze?
Keine Wiederverwendbarkeit über die Definitionsstelle hinaus, fehlender referenzierbarer Name.