Anonymous Classes in PHP: Metaprogramming Without a Named Class Declaration
AI generated
<?php
8.4
PHP · Anonymous Classes · Metaprogramming
Anonymous Classes in PHP
Metaprogramming without a named class declaration

Anonymous classes let you define a fully fledged class right at the point of use with new class(), without assigning a global name. Used correctly, they replace mocking libraries in tests, avoid unnecessary class names for one off implementations, and let you write more compact yet type safe code.

17 min read new class() · Interfaces · Test doubles PHP 7.0+ · 8.4

1. What anonymous classes are and how they differ from named ones

An anonymous class is a class declaration without its own name, defined and immediately instantiated right at its point of use with new class() {...}. Since PHP 7.0, this language feature has been part of the core and works syntactically identical to a normal class: properties, methods, constructors, implemented interfaces and even inherited parent classes are all allowed, only the identifier after the class keyword is missing.

The purpose of this feature is reducing boilerplate for cases where a class is only needed at exactly one place in the code and its own, globally visible class name would provide no added value, but rather generate noise. Instead of creating a named class in its own file for a one off use case that nobody except that single call site will ever reuse, an anonymous class bundles definition and use in a single, easily traceable location in the code.

Important for context: anonymous classes are not a general replacement for regular classes. They are a targeted tool for local, often test related or configuration driven use cases. Anyone who wants to reuse a class across multiple places in the project should always prefer a named class, because anonymous classes do not carry a stable, code referenceable name.

2. Syntax and fundamentals: new class() {...} in detail

The basic syntax of an anonymous class is new class { ... }, where the parentheses for constructor arguments can be omitted entirely if no constructor is needed. Inside the curly braces, exactly the same syntax applies as for a named class: properties with visibility modifiers, typed methods, constants, and even nested anonymous classes as the return value of a method are all allowed.

A common early stumbling block: an anonymous class cannot be used directly in a constant or in a property default declaration, because PHP only allows constant expressions in those positions and new class() is a runtime expression. Anonymous classes are therefore strictly bound to execution contexts where arbitrary expressions may be evaluated, such as inside a function, a method, or directly as a return value.


<?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. Constructor arguments and interfaces on anonymous classes

Anonymous classes support constructors with any parameters, exactly like named classes, including constructor property promotion. The arguments are passed directly in the parentheses after new class(...), which ties definition and instantiation even more tightly together: the caller sees immediately which concrete values the class works with, without having to jump to another location in the code.

Likewise, anonymous classes can implement one or more interfaces, which in practice is the most important use case. The return type of a function then stays stable and type safe, because it refers to the implemented interface, while the concrete implementation remains hidden as an anonymous class and never needs to be referenced by name from outside. This pattern is a lean alternative to the classic strategy pattern whenever only a single, local implementation variant is needed.


<?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. Practical example: test doubles without a mocking framework

Probably the most common practical use of anonymous classes is writing test doubles in unit tests without needing a heavyweight mocking library for it. Instead of a generated mock instance with configured expectations, an anonymous class provides a real, minimal implementation of an interface whose behavior is tailored exactly to the given test case and remains fully readable right inside the test itself.

This technique is particularly resilient against refactorings of the mocking library, because no additional test framework API is involved. The test relies purely on regular PHP language features: interface, anonymous class, constructor arguments. For simple fake implementations, such as an in memory repository standing in for a database, this approach is often more readable than a mock configuration with several chained expectation calls.


<?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. Practical example: one off event listeners and callback objects

Besides tests, anonymous classes are well suited for event listeners or callback objects that are only registered at exactly one place and are more complex than a simple closure, for example because they need to share internal state across several methods. Where a closure only encapsulates a single function, an anonymous class can bundle several related methods and properties without requiring its own, globally visible class.

A typical example is an event listener that both needs to react to an event and afterwards expose an internal counter or collected results. A closure could only model that through use (&$state) and external reference variables, which quickly becomes hard to follow. The anonymous class makes the same state explicitly visible as a property, while staying fully local to the registration site.


<?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. Anonymous classes and inheritance: extends in detail

Anonymous classes can likewise extend an existing named class with extends to selectively override its behavior, without creating an entirely new named subclass for that single, one off adjustment. This is particularly useful in tests when a single method of a parent class needs to show different behavior for a specific test case, while the rest of the class stays unchanged.

When combining extends and implements, the same rules apply as for named classes: an anonymous class can inherit from exactly one class, while implementing any number of additional interfaces. Abstract methods of the parent class must still be fully implemented, PHP makes no exception here just because the class name is missing.


<?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 on anonymous classes: names, caching and identity

Even though anonymous classes carry no name visible in the source code, PHP internally still assigns every anonymous class a generated class name, visible via get_class() or ReflectionObject. This name typically takes the form class@anonymous followed by the file path and line number, which allows tracing back to the definition site in stack traces and error messages, even without a real identifier.

For reflection based tools it is important to know that PHP reuses the same anonymous class definition when the same code section runs multiple times, for example inside a loop, rather than declaring a new class on every iteration, as long as the definition in the source code does not change. Two instances from the same new class() expression therefore share the same underlying class, which get_class($a) === get_class($b) confirms, even though both objects carry different state.


<?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 and memory: how PHP handles anonymous classes internally

PHP compiles an anonymous class the first time the corresponding line of code is reached, and afterwards stores it in the internal class cache exactly like a named class. Repeated execution of the same new class() line, for example inside a loop with a thousand iterations, therefore does not create a thousand different class definitions, but always just new instances of the single, already compiled class. The overhead of an anonymous class compared to a named class is practically unmeasurable at runtime.

A point occasionally overlooked in practice: OPcache treats anonymous classes like any other class definition and caches the compiled bytecode between requests, provided the file containing the anonymous class does not need to be reloaded. There is therefore no performance reason to avoid anonymous classes. The only relevant downside is purely structural in nature: missing reusability beyond the definition site, not runtime overhead.

9. Anonymous classes compared to closures and named classes

The choice between an anonymous class, a closure and a named class depends on the concrete use case. The following table shows when which variant is the better choice.

Use case Recommended tool Reason
Passing a single function as a value Closure No interface, no shared state across methods needed
Interface implementation used in one place only Anonymous class Type safe, no unnecessary global class
Test double with several methods Anonymous class Replaces mocking framework, stays readable
Reuse across multiple locations Named class Referenceable name for reuse
State plus several related methods, local Anonymous class Clearer than a closure with use(&$state)

The practical difference between a closure and an anonymous class lies in the number of public entry points: a closure is always exactly one callable function, an anonymous class can offer any number of methods and several interfaces at once. As soon as more than one method or an interface contract with multiple methods is involved, the anonymous class is almost always the clearer solution.

Mironsoft

PHP test architecture and lean, maintainable codebases

Reducing test code and boilerplate?

We build test suites and application code that deliberately use modern PHP language features like anonymous classes to avoid mocking overhead and unnecessary class sprawl.

Test refactoring

Replacing mocking frameworks with lean test doubles

Code review

Spotting and consolidating unnecessary one off classes

PHP training

Establishing modern language features across your team

10. Summary

Anonymous classes in PHP let you define a fully fledged class right at the point of use with new class() and instantiate it immediately, including constructor arguments, implemented interfaces and inheritance from an existing class. The biggest practical benefit lies in test doubles without a mocking library and in local event listeners or strategy implementations that are only needed at exactly one place in the code.

Internally, PHP treats anonymous classes like any other class: they are compiled once, cached in OPcache, and repeated execution of the same line of code creates no new class definitions, only new instances. The only real downside is structural in nature: missing reusability beyond the definition site. Anyone who needs an implementation in several places should always choose a named class.

Anonymous Classes in PHP — The Essentials at a Glance

Syntax

new class(...) implements X extends Y {...} — identical to a named class, just without a name.

Main use

Test doubles without a mocking framework, local event listeners and one off strategy implementations.

Performance

Compiled once, cached in OPcache, no measurable overhead compared to named classes.

Limit

No reusability beyond the definition site, use a named class in that case.

11. FAQ: Anonymous Classes in PHP

1What is an anonymous class?
A class declaration without a name, defined and instantiated with new class(), supporting the same features as a named class since PHP 7.0.
2Constructor possible?
Yes, with any parameters and property promotion, arguments passed directly after new class(...).
3Implement an interface?
Yes, with implements, even several at once. Most common use case for test doubles.
4Inheritance possible?
Yes, with extends an existing class can be extended, useful for tests needing different behavior in one method.
5Internal name?
Form class@anonymous with file path and line number, visible via get_class() or ReflectionObject::isAnonymous().
6Slower than a named class?
No, compiled and cached identically, no measurable overhead.
7Closure instead?
For a single function without an interface. With several methods or an interface contract, the anonymous class is clearer.
8Instead of a mocking framework?
Yes, most common use: a real, minimal interface implementation without an additional mocking API.
9Same class across instances?
Yes, instances from the same line share the same generated class.
10Biggest limit?
No reusability beyond the definition site, missing referenceable name.