Anonymous classes — use cases and limitations
Intermediate5 min read·php-07-017
compare
Concept
Anonymous classes (PHP 7.0+) are class definitions without a name. They're declared inline using new class { ... } syntax and are immediately instantiated. They cannot be referenced by name elsewhere — once the variable holding the instance goes out of scope, the class definition is effectively gone.
Use cases:
- Test doubles: Create a quick one-off mock or stub without a dedicated test file.
- Interface adapters: Implement an interface inline for a one-time use case without polluting the codebase with a named class.
- Callback objects: When you need an invokable object with complex state but don't want a named class at file scope.
- Extending for a single test:
new class extends SomeClass { protected function hook(): string { return 'test'; } }— override a single protected method without a full subclass.
Limitations: Anonymous classes cannot be serialized (no stable class name for serialize()). They can't be type-hinted by name. They can extend classes, implement interfaces, and use traits — the full class feature set is available.
Under the hood: PHP assigns each anonymous class a unique internal name based on a hash of its definition. This name is visible in stack traces and get_class() output.
Code Example
php
<?php
declare(strict_types=1);
interface Logger
{
public function log(string $message): void;
}
// Named class approach — creates a file, a class, a name to maintain
class NullLogger implements Logger
{
public function log(string $message): void {} // no-op
}
// Anonymous class — no file, no name, immediate inline
$logger = new class implements Logger {
public function log(string $message): void {} // no-op
};
// Extending a class inline (useful in tests)
abstract class BaseController
{
abstract protected function getTitle(): string;
public function render(): string { return "<h1>{$this->getTitle()}</h1>"; }
}
$controller = new class extends BaseController {
protected function getTitle(): string { return 'Test Page'; }
};
echo $controller->render(); // "<h1>Test Page</h1>"
// Test doubles — stub without a mock library
interface UserRepository
{
public function findById(int $id): ?array;
public function save(array $user): void;
}
function testWithStub(): void
{
$users = [1 => ['name' => 'Alice'], 2 => ['name' => 'Bob']];
$repo = new class($users) implements UserRepository {
public function __construct(private array $data) {}
public function findById(int $id): ?array { return $this->data[$id] ?? null; }
public function save(array $user): void { $this->data[] = $user; }
};
assert($repo->findById(1)['name'] === 'Alice');
}
// Anonymous class with constructor arguments
function createFormatter(string $prefix, string $suffix): object
{
return new class($prefix, $suffix) {
public function __construct(
private string $prefix,
private string $suffix,
) {}
public function format(string $value): string
{
return $this->prefix . $value . $this->suffix;
}
};
}
$boldFormatter = createFormatter('<b>', '</b>');
echo $boldFormatter->format('Hello'); // "<b>Hello</b>"