Anonymous Classes
What Is an Anonymous Class?
In simple terms: An anonymous class is a class without a name, defined and instantiated in a single expression. It is useful when you need a small, one-off object — for example, a fake logger in a test or a tiny adapter — and creating a separate named class file would be overkill.
Technically: Since PHP 7.0, the expression new class (...) extends ... implements ... { ... } declares a class and creates an instance of it at the same time. An anonymous class can have properties, methods, a constructor, constants, and traits; it can extend a class and implement interfaces. The object behaves like any other object, and instanceof checks against its parent class and interfaces work normally.
Syntax
$object = new class (constructorArgs) extends ParentClass implements SomeInterface {
// properties, methods, constants, use Trait;
};
Everything after new class is optional except the class body.
A First Example
<?php
interface Logger
{
public function log(string $message): void;
}
$logger = new class implements Logger {
public array $messages = [];
public function log(string $message): void
{
$this->messages[] = $message;
}
};
$logger->log('User asha logged in');
$logger->log('Order 501 placed');
var_dump($logger instanceof Logger);
print_r($logger->messages);
Output
bool(true)
Array
(
[0] => User asha logged in
[1] => Order 501 placed
)
How It Works
new class implements Logger { ... }declares an unnamed class that implementsLoggerand immediately creates one object of it.- The object can be passed anywhere a
Loggeris expected. - The
messagesproperty lets us inspect what was logged — a typical test double.
Constructor Arguments, Inheritance, and Traits
<?php
interface TaxRule
{
public function apply(float $amount): float;
}
abstract class BaseRule implements TaxRule
{
protected function round(float $value): float
{
return round($value, 2);
}
}
trait Describable
{
public function describe(): string
{
return static::class === self::class ? 'rule' : 'anonymous rule';
}
}
$gst = new class (18) extends BaseRule {
use Describable;
public function __construct(private float $percent) {}
public function apply(float $amount): float
{
return $this->round($amount * (1 + $this->percent / 100));
}
};
echo $gst->apply(1299) . "\n";
var_dump($gst instanceof TaxRule, $gst instanceof BaseRule);
Output
1532.82
bool(true)
bool(true)
- Arguments after
new class(here18) are passed to the anonymous class's constructor. extends BaseRulelets it reuse the parent's protectedround()helper and inherit theTaxRuleinterface.- Traits can be used just as in named classes.
The Name of an Anonymous Class
Anonymous classes do have an internal name, generated by PHP:
<?php
interface Logger
{
public function log(string $message): void;
}
$logger = new class implements Logger {
public function log(string $message): void {}
};
$plain = new class {};
echo strstr(get_class($logger), "\0", true) . "\n";
echo strstr(get_class($plain), "\0", true) . "\n";
var_dump((new ReflectionClass($logger))->isAnonymous());
Output
Logger@anonymous
class@anonymous
bool(true)
- Since PHP 8.0, the generated name starts with the parent class or first interface name followed by
@anonymous(orclass@anonymousif there is neither), which makes error messages and logs more readable. - The full name also contains a NUL byte followed by the file path and position;
strstr(..., "\0", true)cuts it off for display. - Do not depend on the generated name in code; use
instanceoforReflectionClass::isAnonymous().
One Class per Declaration
Each new class expression in the source code defines one class. Running the same expression several times creates several objects of the same class:
<?php
function makePoint(int $x, int $y): object
{
return new class ($x, $y) {
public function __construct(public int $x, public int $y) {}
};
}
$p1 = makePoint(1, 2);
$p2 = makePoint(3, 4);
var_dump(get_class($p1) === get_class($p2));
var_dump($p1 == makePoint(1, 2));
Output
bool(true)
bool(true)
Because both come from the same declaration, the objects share a class and can be compared with ==.
Practical Example: Test Doubles Without Extra Files
Anonymous classes shine in tests, where a dependency needs a small, controllable implementation:
<?php
interface ExchangeRateProvider
{
public function rate(string $from, string $to): float;
}
final class PriceConverter
{
public function __construct(private ExchangeRateProvider $rates) {}
public function toInr(float $amount, string $currency): float
{
if ($currency === 'INR') {
return $amount;
}
return round($amount * $this->rates->rate($currency, 'INR'), 2);
}
}
// Test: a fake provider with fixed rates and a call counter
$fakeRates = new class implements ExchangeRateProvider {
public int $calls = 0;
public function rate(string $from, string $to): float
{
$this->calls++;
return ['USD' => 83.2, 'EUR' => 90.5][$from] ?? throw new InvalidArgumentException("No rate for $from");
}
};
$converter = new PriceConverter($fakeRates);
echo $converter->toInr(25, 'USD') . "\n";
echo $converter->toInr(10, 'EUR') . "\n";
echo $converter->toInr(500, 'INR') . "\n";
echo "Provider calls: {$fakeRates->calls}\n";
Output
2080
905
500
Provider calls: 2
Why This Works Well
- The real provider would call an external API; the anonymous class returns fixed, predictable rates.
- The
callscounter verifies that INR amounts do not trigger a rate lookup. - The fake lives right next to the test that uses it, which makes the test easy to read.
Readonly Anonymous Classes (PHP 8.3+)
Like named classes, anonymous classes can be declared readonly, making all their properties readonly:
<?php
$config = new readonly class ('GoCourse', 'ta') {
public function __construct(public string $appName, public string $language) {}
};
echo $config->appName . " ({$config->language})\n";
Output
GoCourse (ta)
When to Use Anonymous Classes
| Good Fit | Better as a Named Class |
|---|---|
| Test doubles for interfaces | Logic reused in several places |
| Small adapters used in exactly one place | Classes with significant behavior |
| Simple event listeners or callbacks needing state | Anything that should be documented, autoloaded, or found by name |
| Prototyping | Production domain objects |
For stateless one-off behavior, a closure is often simpler than an anonymous class. Choose an anonymous class when the code must satisfy an interface or extend a class.
Common Mistakes
Large Anonymous Classes
Effect: Hard-to-read code with logic hidden inside expressions.
Fix: Extract a named class.
Relying on the Generated Class Name
Effect: Code breaks when the file or line changes.
Fix: Use instanceof with interfaces or parent classes.
Forgetting the Semicolon
Effect: Syntax error — new class { ... } is an expression and needs a ; after the closing brace when used as a statement.
Fix: End the statement with };.
Serializing Anonymous Objects
Effect: serialize() throws an exception: anonymous classes cannot be serialized.
Fix: Use named classes for objects that are serialized, cached, or queued.
Best Practices
- Use anonymous classes for small, local implementations of interfaces, especially in tests.
- Keep them short; extract a named class as soon as logic grows or is reused.
- Pass dependencies through the constructor arguments of
new class (...). - Use closures instead when no interface or parent class is needed.
- Never depend on generated class names.
Official reference: https://www.php.net/manual/en/language.oop5.anonymous.php