Основы
Анонимные классы (PHP 7.0+) позволяют создавать объекты без предварительного объявления класса. Они полезны для одноразовых объектов -- заглушек в тестах, простых реализаций интерфейсов, lightweight-адаптеров.
<?php
declare(strict_types=1);
// Basic anonymous class
$obj = new class {
public function greet(): string
{
return 'Hello from anonymous class!';
}
};
echo $obj->greet(); // Hello from anonymous class!
// Type checking
var_dump($obj instanceof stdClass); // false
var_dump(get_class($obj)); // class@anonymous/path/to/file.php:3$0
var_dump(get_debug_type($obj)); // class@anonymous
Конструктор и аргументы
<?php
declare(strict_types=1);
// Anonymous class with constructor arguments
$user = new class('Alice', '[email protected]') {
public function __construct(
private readonly string $name,
private readonly string $email,
) {
}
public function toString(): string
{
return "{$this->name} <{$this->email}>";
}
};
echo $user->toString(); // Alice <[email protected]>
// With constructor property promotion
$point = new class(3.0, 4.0) {
public function __construct(
public readonly float $x,
public readonly float $y,
) {
}
public function distanceFromOrigin(): float
{
return sqrt($this->x ** 2 + $this->y ** 2);
}
};
echo $point->x; // 3
echo $point->distanceFromOrigin(); // 5
Наследование и реализация интерфейсов
<?php
declare(strict_types=1);
interface Logger
{
public function log(string $message): void;
}
abstract class AbstractLogger implements Logger
{
abstract protected function write(string $formatted): void;
public function log(string $message): void
{
$formatted = sprintf('[%s] %s', date('Y-m-d H:i:s'), $message);
$this->write($formatted);
}
}
// Anonymous class extending abstract class
$consoleLogger = new class extends AbstractLogger {
protected function write(string $formatted): void
{
echo $formatted . "\n";
}
};
$consoleLogger->log('Application started');
// Anonymous class implementing interface
$nullLogger = new class implements Logger {
public function log(string $message): void
{
// Intentionally empty — null object pattern
}
};
// Multiple interfaces
interface Countable
{
public function count(): int;
}
$collection = new class([1, 2, 3]) implements Logger, \Countable {
public function __construct(
private readonly array $items,
) {
}
public function log(string $message): void
{
echo $message;
}
public function count(): int
{
return count($this->items);
}
};
echo $collection->count(); // 3
// Type checking works with interfaces
var_dump($nullLogger instanceof Logger); // true
var_dump($collection instanceof \Countable); // true
// Accept by interface type
function processLogger(Logger $logger): void
{
$logger->log('Processing...');
}
processLogger($nullLogger); // OK
processLogger($consoleLogger); // OK
Практика: Анонимные классы хорошо работают с интерфейсами -- вы можете передавать их везде, где ожидается интерфейс, без создания отдельного именованного класса.
Использование в тестах (Mock/Stub)
<?php
declare(strict_types=1);
interface UserRepository
{
public function findById(int $id): ?array;
public function save(array $user): void;
}
interface Mailer
{
public function send(string $to, string $subject, string $body): bool;
}
class UserService
{
public function __construct(
private readonly UserRepository $repository,
private readonly Mailer $mailer,
) {
}
public function register(string $name, string $email): array
{
$user = ['name' => $name, 'email' => $email, 'id' => random_int(1, 1000)];
$this->repository->save($user);
$this->mailer->send($email, 'Welcome!', "Hello, {$name}!");
return $user;
}
}
// In test — create inline stubs with anonymous classes
function testRegister(): void
{
$savedUsers = [];
$repository = new class($savedUsers) implements UserRepository {
/** @param list<array> $saved */
public function __construct(
private array &$saved,
) {
}
public function findById(int $id): ?array
{
return null; // Stub
}
public function save(array $user): void
{
$this->saved[] = $user;
}
};
$sentEmails = [];
$mailer = new class($sentEmails) implements Mailer {
/** @param list<array> $sent */
public function __construct(
private array &$sent,
) {
}
public function send(string $to, string $subject, string $body): bool
{
$this->sent[] = ['to' => $to, 'subject' => $subject, 'body' => $body];
return true;
}
};
$service = new UserService($repository, $mailer);
$user = $service->register('Alice', '[email protected]');
assert($user['name'] === 'Alice');
assert(count($savedUsers) === 1);
assert(count($sentEmails) === 1);
assert($sentEmails[0]['to'] === '[email protected]');
echo "Test passed!\n";
}
testRegister();
Использование в фабриках
<?php
declare(strict_types=1);
interface Renderer
{
public function render(string $template, array $data): string;
}
class RendererFactory
{
public static function create(string $type): Renderer
{
return match ($type) {
'json' => new class implements Renderer {
public function render(string $template, array $data): string
{
return json_encode($data, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT);
}
},
'csv' => new class implements Renderer {
public function render(string $template, array $data): string
{
$output = fopen('php://temp', 'r+');
foreach ($data as $row) {
fputcsv($output, (array) $row);
}
rewind($output);
$result = stream_get_contents($output);
fclose($output);
return $result;
}
},
'text' => new class implements Renderer {
public function render(string $template, array $data): string
{
return str_replace(
array_map(fn(string $k): string => "{{$k}}", array_keys($data)),
array_values($data),
$template,
);
}
},
default => throw new InvalidArgumentException("Unknown type: {$type}"),
};
}
}
$renderer = RendererFactory::create('text');
echo $renderer->render('Hello, {name}!', ['name' => 'Alice']);
// Hello, Alice!
Scope и доступ к внешним переменным
<?php
declare(strict_types=1);
class Outer
{
private string $secret = 'hidden';
protected int $count = 0;
public function createInner(): object
{
// Anonymous class INSIDE a method has access to $this of outer class
// BUT only through captured variables, not direct scope
$secret = $this->secret; // Capture the value
return new class($secret) {
public function __construct(
private readonly string $captured,
) {
}
public function reveal(): string
{
return $this->captured;
}
};
}
public function createNested(): object
{
// Anonymous class defined inside a class method:
// It does NOT inherit the outer class scope
// It's a completely separate class
return new class {
// Cannot access $this->secret from Outer
// Cannot access $this->count from Outer
public function test(): string
{
return 'I am independent';
}
};
}
}
$outer = new Outer();
$inner = $outer->createInner();
echo $inner->reveal(); // 'hidden'
Важно: Анонимный класс, определенный внутри метода, НЕ наследует scope внешнего класса. Он не имеет прямого доступа к
$thisвнешнего класса. Переменные нужно передавать явно через конструктор.
get_class() и идентификация
<?php
declare(strict_types=1);
$a = new class {};
$b = new class {};
// Each anonymous class has a unique internal name
echo get_class($a); // class@anonymous/path/file.php:3$0
echo get_class($b); // class@anonymous/path/file.php:4$1
// Same definition, same location = same class
function factory(): object
{
return new class {
public int $x = 0;
};
}
$obj1 = factory();
$obj2 = factory();
// Objects from the same anonymous class definition ARE the same class
echo get_class($obj1) === get_class($obj2) ? 'Same' : 'Different'; // Same
// instanceof works
var_dump($obj1 instanceof (get_class($obj2))); // true
// Comparison
var_dump($obj1 == $obj2); // true (same class, same property values)
var_dump($obj1 === $obj2); // false (different instances)
// get_debug_type for cleaner output
echo get_debug_type($a); // class@anonymous
Вложенные анонимные классы
<?php
declare(strict_types=1);
// Anonymous class returning another anonymous class
$factory = new class {
public function createLogger(): object
{
return new class {
private array $logs = [];
public function log(string $message): void
{
$this->logs[] = $message;
}
/** @return list<string> */
public function getLogs(): array
{
return $this->logs;
}
};
}
public function createCounter(): object
{
return new class {
private int $count = 0;
public function increment(): void
{
$this->count++;
}
public function getCount(): int
{
return $this->count;
}
};
}
};
$logger = $factory->createLogger();
$logger->log('First');
$logger->log('Second');
var_dump($logger->getLogs()); // ['First', 'Second']
Когда использовать анонимные классы vs именованные
<?php
declare(strict_types=1);
// USE anonymous classes when:
// 1. One-time use — stub in tests
// 2. Simple interface implementation — 1-3 methods
// 3. Factory pattern with inline variants
// 4. Null Object pattern
// 5. Quick adapter or wrapper
// DO NOT use anonymous classes when:
// 1. Class is reused in multiple places
// 2. Class has complex logic (> ~20 lines)
// 3. Class needs to be type-hinted by name
// 4. Class needs to be serialized/deserialized
// 5. Class needs to be extended
// Good: null object
interface Cache
{
public function get(string $key): mixed;
public function set(string $key, mixed $value, int $ttl): void;
}
$nullCache = new class implements Cache {
public function get(string $key): mixed { return null; }
public function set(string $key, mixed $value, int $ttl): void {}
};
// Bad: complex logic should be a named class
// $complex = new class {
// // 100+ lines of business logic...
// // Hard to test, hard to reuse
// };