PHP: ООП

40 вопросов

1 Как реализовано ООП в PHP?

PHP поддерживает классическое ООП на основе классов с PHP 5+:

  • Классы и объекты - шаблоны и их экземпляры
  • Наследование - одиночное (extends), множественное через трейты
  • Полиморфизм - через интерфейсы и наследование
  • Инкапсуляция - модификаторы видимости (public, protected, private)
  • Абстракция - абстрактные классы и интерфейсы

С PHP 8.0+ появились: именованные аргументы, enum, readonly свойства, типизированные свойства, конструктор property promotion.

Открыть отдельно →
2 Чем класс отличается от объекта?

Класс - описание (blueprint): свойства, методы, константы. Объект - конкретный экземпляр класса в памяти.

class Car {                    // Класс
    public string $color;
    public function drive() {}
}

$myCar = new Car();            // Объект (экземпляр)
$myCar->color = 'red';

$yourCar = new Car();          // Другой объект того же класса
$yourCar->color = 'blue';

Класс существует один раз в коде. Объектов одного класса может быть сколько угодно, каждый со своими значениями свойств.

Открыть отдельно →
3 Что такое модификаторы видимости?
class User {
    public string $name;      // доступен отовсюду
    protected int $age;        // доступен в классе и наследниках
    private string $password;  // доступен только в этом классе

    public function getName(): string { return $this->name; }
    protected function validate(): bool { /* ... */ }
    private function hashPassword(): string { /* ... */ }
}

С PHP 8.4 появилась асимметричная видимость:

class User {
    public private(set) string $name; // чтение public, запись private
}

Рекомендация: делайте все private по умолчанию, расширяя видимость по необходимости.

Открыть отдельно →
4 Что такое интерфейсы? Чем отличаются от абстрактных классов?

Интерфейс - контракт, описывающий набор методов без реализации:

interface Cacheable {
    public function getCacheKey(): string;
    public function getCacheTTL(): int;
}

class User implements Cacheable {
    public function getCacheKey(): string { return "user:{$this->id}"; }
    public function getCacheTTL(): int { return 3600; }
}

Различия от абстрактных классов:

  • Интерфейс не может содержать реализацию (кроме констант)
  • Класс может реализовывать несколько интерфейсов, но наследовать только один класс
  • Интерфейс не может иметь свойств
  • Абстрактный класс может содержать и абстрактные, и обычные методы
Открыть отдельно →
5 Может ли абстрактный класс содержать private методы?

Да, абстрактный класс может содержать private обычные (не абстрактные) методы:

abstract class Base {
    private function helper(): string {
        return 'internal logic';
    }

    public function process(): string {
        return $this->helper();
    }

    abstract public function execute(): void;
}

Однако private абстрактный метод не имеет смысла и вызовет ошибку - наследник не сможет его переопределить. Абстрактные методы должны быть public или protected.

Открыть отдельно →
6 Можно ли создать цепочку наследования между абстрактными классами?

Да, абстрактный класс может наследовать другой абстрактный класс:

abstract class Animal {
    abstract public function speak(): string;
    public function breathe(): string { return 'breathing'; }
}

abstract class Pet extends Animal {
    abstract public function getName(): string;
    // speak() все еще абстрактный
}

class Dog extends Pet {
    public function speak(): string { return 'Woof!'; }
    public function getName(): string { return 'Rex'; }
}

Промежуточный абстрактный класс может реализовать часть абстрактных методов и добавить новые.

Открыть отдельно →
7 Что такое трейты (traits)?

Трейты - механизм повторного использования кода без наследования (горизонтальное переиспользование):

trait Timestampable {
    public ?DateTime $createdAt = null;
    public function setCreatedAt(): void {
        $this->createdAt = new DateTime();
    }
}

trait SoftDeletable {
    public ?DateTime $deletedAt = null;
    public function softDelete(): void {
        $this->deletedAt = new DateTime();
    }
}

class User {
    use Timestampable, SoftDeletable;
}

Трейты не нарушают SOLID если используются для переиспользования инфраструктурного кода. Проблемы: неявные зависимости, конфликты имен, сложность отладки.

Открыть отдельно →
8 Что делать, если у двух трейтов метод с одинаковым именем?
trait A {
    public function hello(): string { return 'A'; }
}

trait B {
    public function hello(): string { return 'B'; }
}

class MyClass {
    use A, B {
        A::hello insteadof B;  // использовать метод из A
        B::hello as helloB;     // метод B доступен как helloB
    }
}

$obj = new MyClass();
echo $obj->hello();   // "A"
echo $obj->helloB();  // "B"

Без разрешения конфликта PHP выдаст фатальную ошибку. as также позволяет менять видимость: A::hello as private;

Открыть отдельно →
9 Что такое final класс и final метод?
// final класс - нельзя наследовать
final class Singleton {
    private static ?self $instance = null;
    public static function getInstance(): self {
        return self::$instance ??= new self();
    }
}
// class Child extends Singleton {} // Fatal error!

// final метод - нельзя переопределять в наследниках
class Base {
    final public function critical(): void { /* ... */ }
}
class Child extends Base {
    // public function critical(): void {} // Fatal error!
}

Используйте final когда наследование может сломать логику. В DDD часто Entity и Value Object делают final.

Открыть отдельно →
10 Что такое static и self? В чем разница?
class ParentClass {
    public static function create(): static {
        return new static(); // Late Static Binding
    }

    public static function createSelf(): self {
        return new self(); // всегда ParentClass
    }
}

class ChildClass extends ParentClass {}

$obj = ChildClass::create();     // ChildClass
$obj = ChildClass::createSelf(); // ParentClass!

self ссылается на класс, в котором написан код (compile-time). static ссылается на класс, который вызвал метод (runtime). $this - ссылка на текущий объект (только в нестатическом контексте).

Открыть отдельно →
11 Чем self:: отличается от static::? (позднее статическое связывание)

self:: привязано к классу, в котором записан код. static:: привязано к классу, который вызвал метод (Late Static Binding, PHP 5.3+).

class Model {
    protected static string $table = 'models';

    public static function getTable(): string {
        return static::$table; // берет $table из вызывающего класса
    }
}

class User extends Model {
    protected static string $table = 'users';
}

echo User::getTable(); // "users" (static::)
// Если бы было self::$table - вернул бы "models"

LSB критически важен для паттернов Active Record, Factory Method.

Открыть отдельно →
12 Чем this отличается от self?

$this - ссылка на текущий объект (экземпляр класса). Доступен только в нестатическом контексте. self - ссылка на текущий класс. Доступен в статическом и нестатическом контексте.

class Counter {
    private int $count = 0;
    private static int $total = 0;

    public function increment(): void {
        $this->count++;     // свойство объекта
        self::$total++;     // статическое свойство класса
    }

    public static function getTotal(): int {
        // $this не доступен здесь!
        return self::$total;
    }
}
Открыть отдельно →
13 Что такое позднее статическое связывание (Late Static Binding)?

LSB позволяет ссылаться на класс, который реально вызвал статический метод, а не на класс, в котором метод определен:

class Base {
    public static function create(): static {
        return new static(); // класс вызова, не определения
    }
}

class User extends Base {}
class Admin extends Base {}

$user = User::create();  // User, а не Base
$admin = Admin::create(); // Admin, а не Base

Ключевое слово static (вместо self) реализует LSB. Это основа для паттернов Active Record (Eloquent), Factory Method, и любых иерархий с наследуемым поведением.

Открыть отдельно →
14 Что такое магические методы?

Магические методы начинаются с __ и вызываются при определенных событиях:

  • __construct() / __destruct() - создание/уничтожение объекта
  • __get($name) / __set($name, $val) - доступ к несуществующим/private свойствам
  • __call($name, $args) / __callStatic() - вызов несуществующих методов
  • __toString() - преобразование в строку
  • __invoke() - вызов объекта как функции
  • __clone() - клонирование объекта
  • __sleep() / __wakeup() - сериализация/десериализация
  • __serialize() / __unserialize() - PHP 7.4+, замена sleep/wakeup
  • __debugInfo() - вывод var_dump
  • __isset($name) / __unset($name) - isset/unset на свойствах
Открыть отдельно →
15 Когда вызывается __set()? Когда нужен __get()?
class Config {
    private array $data = [];

    // Вызывается при записи в несуществующее/недоступное свойство
    public function __set(string $name, mixed $value): void {
        $this->data[$name] = $value;
    }

    // Вызывается при чтении несуществующего/недоступного свойства
    public function __get(string $name): mixed {
        return $this->data[$name] ?? null;
    }

    public function __isset(string $name): bool {
        return isset($this->data[$name]);
    }
}

$cfg = new Config();
$cfg->host = 'localhost'; // __set()
echo $cfg->host;          // __get()

Используются для: dynamic properties, прокси-объектов, ORM. Злоупотребление усложняет отладку и IDE-подсказки.

Открыть отдельно →
16 Для чего __invoke()?

__invoke() позволяет вызывать объект как функцию:

class Multiplier {
    public function __construct(private int $factor) {}

    public function __invoke(int $value): int {
        return $value * $this->factor;
    }
}

$double = new Multiplier(2);
echo $double(5);   // 10
echo $double(10);  // 20

// Можно передавать как callable
$result = array_map(new Multiplier(3), [1, 2, 3]); // [3, 6, 9]

Паттерн Single Action Class - класс с единственным действием: валидаторы, конвертеры, pipeline-шаги.

Открыть отдельно →
17 Что такое клонирование объектов?
class Order {
    public array $items = [];
    public Address $address;

    public function __clone() {
        // Без __clone: $address будет общим (shallow copy)
        $this->address = clone $this->address; // deep copy
        // Массив скаляров копируется автоматически
    }
}

$order1 = new Order();
$order2 = clone $order1; // вызывает __clone()

Shallow copy (по умолчанию) - копирует примитивы, но объекты и ресурсы остаются общими. Deep copy - нужно реализовать через __clone(), клонируя вложенные объекты.

Открыть отдельно →
18 Что такое сериализация объектов?
// Старый способ (PHP < 7.4)
class OldStyle {
    public function __sleep(): array {
        return ['name', 'age']; // какие свойства сериализовать
    }
    public function __wakeup(): void {
        $this->connection = new DB(); // восстановить ресурсы
    }
}

// Новый способ (PHP 7.4+)
class NewStyle {
    public function __serialize(): array {
        return ['name' => $this->name, 'age' => $this->age];
    }
    public function __unserialize(array $data): void {
        $this->name = $data['name'];
        $this->age = $data['age'];
    }
}

$serialized = serialize($obj);
$obj = unserialize($serialized);

Новые методы дают полный контроль над форматом данных. Для API используйте JSON, не serialize().

Открыть отдельно →
19 Что такое конструктор и деструктор?
class Connection {
    private $resource;

    public function __construct(string $dsn) {
        $this->resource = pg_connect($dsn);
    }

    public function __destruct() {
        if ($this->resource) {
            pg_close($this->resource);
        }
    }
}

// PHP 8.0: Constructor Property Promotion
class User {
    public function __construct(
        public readonly string $name,
        public readonly int $age,
    ) {}
}

Деструктор вызывается при уничтожении объекта (когда refcount = 0 или при завершении скрипта). Порядок вызова деструкторов не гарантирован.

Открыть отдельно →
20 Что такое embedding (встраивание)?

В PHP нет прямого аналога Go embedding. Ближайший механизм - трейты и делегирование:

// Трейт - встраивание поведения
trait HasTimestamps {
    public DateTime $createdAt;
    public DateTime $updatedAt;
}

class User {
    use HasTimestamps; // "встраивает" свойства и методы
}

// Делегирование (composition)
class UserService {
    public function __construct(
        private UserRepository $repo, // композиция
    ) {}

    public function find(int $id): User {
        return $this->repo->find($id); // делегируем
    }
}

В Go embedding автоматически проксирует методы; в PHP нужно явно делегировать или использовать __call().

Открыть отдельно →
21 Что такое полиморфизм? Как реализуется?

Полиморфизм - способность объектов разных классов обрабатывать одно и то же сообщение по-своему:

interface NotificationChannel {
    public function send(string $message): void;
}

class EmailChannel implements NotificationChannel {
    public function send(string $message): void {
        mail($this->to, 'Notification', $message);
    }
}

class SlackChannel implements NotificationChannel {
    public function send(string $message): void {
        $this->api->post($this->webhook, $message);
    }
}

// Полиморфный вызов - код не знает конкретный тип
function notify(NotificationChannel $channel, string $msg): void {
    $channel->send($msg); // разное поведение
}

Реализуется через интерфейсы (наиболее правильно) и наследование с переопределением методов.

Открыть отдельно →
22 Что такое инкапсуляция?

Инкапсуляция - не просто сокрытие данных (это лишь один аспект). Это объединение данных и методов работы с ними в единый блок с контролируемым доступом:

class BankAccount {
    private float $balance = 0;

    public function deposit(float $amount): void {
        if ($amount <= 0) throw new InvalidArgumentException();
        $this->balance += $amount;
    }

    public function withdraw(float $amount): void {
        if ($amount > $this->balance) throw new InsufficientFundsException();
        $this->balance -= $amount;
    }

    public function getBalance(): float {
        return $this->balance;
    }
}

Ключевой смысл: класс гарантирует свои инварианты. Нельзя установить отрицательный баланс, потому что доступ к $balance только через методы с валидацией.

Открыть отдельно →
23 Что такое проблема наследования? Почему композиция лучше?

Проблемы наследования:

  • Tight coupling - изменение базового класса ломает наследников
  • Fragile base class - хрупкость базового класса
  • Diamond problem (через трейты)
  • Нарушение инкапсуляции (protected свойства)
  • Иерархия становится глубокой и сложной

Композиция - объект содержит другие объекты и делегирует им работу:

// Плохо: наследование
class ElectricCar extends Car { /* ... */ }

// Хорошо: композиция
class Car {
    public function __construct(
        private Engine $engine,
        private Transmission $transmission,
    ) {}
}

Правило: используйте наследование для отношения "является" (is-a), композицию для "содержит" (has-a).

Открыть отдельно →
24 Что такое анонимные классы?
// Анонимный класс - одноразовый класс без имени
$logger = new class implements LoggerInterface {
    public function log(string $msg): void {
        echo $msg;
    }
};

// Полезно в тестах для создания mock/stub
$mock = new class extends AbstractRepository {
    public function find(int $id): ?Entity {
        return new Entity(id: $id, name: 'test');
    }
};

// Можно передавать зависимости через конструктор
$handler = new class($db) {
    public function __construct(private DB $db) {}
    public function handle(): void { /* ... */ }
};

Используются для quick implementations, тестов, одноразовых стратегий.

Открыть отдельно →
25 Что такое enum? Backed enum vs unit enum?
// Unit enum - без скалярных значений
enum Suit {
    case Hearts;
    case Diamonds;
    case Clubs;
    case Spades;
}

// Backed enum - с привязкой к int или string
enum Status: string {
    case Active = 'active';
    case Inactive = 'inactive';

    // Могут содержать методы
    public function label(): string {
        return match($this) {
            self::Active => 'Активен',
            self::Inactive => 'Неактивен',
        };
    }
}

// Backed enum: from/tryFrom
$s = Status::from('active');      // Status::Active
$s = Status::tryFrom('unknown');  // null
$s = Status::Active->value;       // 'active'
Status::cases();                   // все значения
Открыть отдельно →
26 Что такое readonly свойства и readonly классы?
// readonly свойство (PHP 8.1)
class User {
    public function __construct(
        public readonly string $name,
        public readonly string $email,
    ) {}
}

$user = new User('John', 'john@example.com');
$user->name = 'Jane'; // Error: Cannot modify readonly property

// readonly класс (PHP 8.2) - все свойства readonly
readonly class Point {
    public function __construct(
        public float $x,
        public float $y,
    ) {}
}

readonly свойство можно установить только один раз (обычно в конструкторе). Нельзя unset. Нельзя изменять после инициализации (даже из самого класса). В PHP 8.3 readonly свойства можно клонировать с изменением.

Открыть отдельно →
27 Что такое property hooks? (PHP 8.4)
class User {
    // Виртуальное свойство с хуками get/set
    public string $fullName {
        get => "$this->firstName $this->lastName";
        set(string $value) {
            [$this->firstName, $this->lastName] = explode(' ', $value, 2);
        }
    }

    // Хук только для set (валидация)
    public string $email {
        set(string $value) {
            if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                throw new InvalidArgumentException();
            }
            $this->email = $value;
        }
    }
}

Property hooks заменяют getters/setters и делают API чище. Это аналог C# properties. Свойства с хуками работают с readonly, интерфейсами и абстрактными классами.

Открыть отдельно →
28 Что такое асимметричная видимость свойств? (PHP 8.4)
class User {
    // Чтение публичное, запись приватная
    public private(set) string $name;

    // Чтение публичное, запись защищенная (для наследников)
    public protected(set) int $age;

    public function __construct(string $name, int $age) {
        $this->name = $name;  // OK - внутри класса
        $this->age = $age;
    }
}

$user = new User('John', 30);
echo $user->name;          // OK
$user->name = 'Jane';     // Error: Cannot modify

Уровень видимости для set не может быть шире, чем для get. Это делает ненужными многие геттеры.

Открыть отдельно →
29 Что такое constructor property promotion? (PHP 8.0)
// До PHP 8.0
class User {
    public string $name;
    public int $age;

    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }
}

// PHP 8.0+ - в 3 раза короче
class User {
    public function __construct(
        public string $name,
        public int $age,
    ) {}
}

// Можно комбинировать с readonly (PHP 8.1)
class User {
    public function __construct(
        public readonly string $name,
        public readonly int $age,
    ) {}
}

Модификатор видимости перед параметром конструктора автоматически создает свойство и присваивает значение.

Открыть отдельно →
30 Что такое Value Object? Как реализовать?
// Value Object - неизменяемый объект, сравнивается по значению
readonly class Money {
    public function __construct(
        public int $amount,      // в копейках
        public string $currency,
    ) {
        if ($amount < 0) throw new InvalidArgumentException('Negative amount');
    }

    public function add(Money $other): self {
        if ($this->currency !== $other->currency) {
            throw new DomainException('Currency mismatch');
        }
        return new self($this->amount + $other->amount, $this->currency);
    }

    public function equals(Money $other): bool {
        return $this->amount === $other->amount
            && $this->currency === $other->currency;
    }
}

Ключевые свойства VO: иммутабельность, сравнение по значению (не по ссылке), самовалидация.

Открыть отдельно →
31 Что такое DTO (Data Transfer Object)?

DTO - объект для передачи данных между слоями приложения без бизнес-логики:

readonly class CreateUserDTO {
    public function __construct(
        public string $name,
        public string $email,
        public int $age,
    ) {}

    public static function fromRequest(Request $request): self {
        return new self(
            name: $request->input('name'),
            email: $request->input('email'),
            age: (int) $request->input('age'),
        );
    }
}

// Использование
class UserService {
    public function create(CreateUserDTO $dto): User {
        return $this->repo->create($dto->name, $dto->email, $dto->age);
    }
}

DTO отличается от Entity (нет бизнес-логики, нет identity) и от Value Object (не обязан быть иммутабельным, нет поведения).

Открыть отдельно →
32 Что такое иммутабельный объект? Как создать?
readonly class Email {
    public function __construct(public string $value) {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Invalid email: $value");
        }
    }

    // Методы возвращают НОВЫЙ объект
    public function withDomain(string $domain): self {
        $local = explode('@', $this->value)[0];
        return new self("$local@$domain");
    }
}

$email = new Email('john@old.com');
$new = $email->withDomain('new.com');
// $email не изменился, $new - новый объект

Правила иммутабельности: readonly свойства, методы-модификаторы возвращают new self(...), нет сеттеров.

Открыть отдельно →
33 Что такое ковариантность и контравариантность?

Ковариантность (PHP 7.4+) - возвращаемый тип в наследнике может быть более конкретным:

interface Factory {
    public function create(): Animal;
}
class DogFactory implements Factory {
    public function create(): Dog { /* ... */ } // Dog extends Animal - OK
}

Контравариантность (PHP 7.4+) - тип параметра в наследнике может быть более общим:

interface Handler {
    public function handle(Dog $animal): void;
}
class AnimalHandler implements Handler {
    public function handle(Animal $animal): void { /* ... */ } // Animal <- Dog - OK
}

Это следствие принципа подстановки Лисков (LSP): наследник должен быть безопасной заменой родителя.

Открыть отдельно →
34 Что такое method set для value и pointer receiver? (Go-аналогия)

В PHP этой проблемы нет - объекты всегда передаются по идентификатору (handle), аналогично указателям в Go.

В Go value receiver (func (v Value) Method()) получает копию, pointer receiver (func (p *Value) Method()) - ссылку. Интерфейс, требующий pointer receiver, не может быть удовлетворен value.

В PHP все объекты ведут себя как Go pointer receivers - методы всегда работают с оригинальным объектом. Клонирование нужно делать явно через clone.

Открыть отдельно →
35 Как получить доступ к private свойствам снаружи?
class Secret {
    private string $password = 'hidden';
}

// Через Reflection API
$obj = new Secret();
$ref = new ReflectionClass($obj);
$prop = $ref->getProperty('password');
$prop->setAccessible(true); // PHP < 8.1 требовался
echo $prop->getValue($obj); // "hidden"

// С PHP 8.1 setAccessible() не нужен
$prop = (new ReflectionClass($obj))->getProperty('password');
echo $prop->getValue($obj); // "hidden"

// Через Closure::bind (хитрый способ)
$getter = Closure::bind(fn() => $this->password, $obj, Secret::class);
echo $getter(); // "hidden"

Reflection используется в DI-контейнерах, ORM, сериализаторах, тестовых фреймворках.

Открыть отдельно →
36 Что такое Reflection API?

Reflection API - механизм интроспекции: анализ классов, методов, свойств, параметров в runtime:

$ref = new ReflectionClass(User::class);
$ref->getMethods();              // все методы
$ref->getProperties();           // все свойства
$ref->getConstructor()->getParameters(); // параметры конструктора
$ref->getAttributes();           // атрибуты (PHP 8.0)
$ref->isAbstract();
$ref->isFinal();
$ref->implementsInterface(Serializable::class);

// ReflectionMethod
$method = new ReflectionMethod(User::class, 'getName');
$method->getReturnType();
$method->invoke($obj);

Применение: DI-контейнеры (autowiring), ORM (маппинг), сериализаторы, генераторы документации, тестовые фреймворки.

Открыть отдельно →
37 Можно ли объявить private метод в интерфейсе?

Нет. Все методы интерфейса неявно public. Интерфейс описывает публичный контракт - что класс обещает внешнему миру. Private методы - это детали реализации, им не место в контракте.

interface UserRepositoryInterface {
    public function find(int $id): ?User;  // OK
    // private function validate(): bool;  // Fatal error!
}
Открыть отдельно →
38 Можно ли объявить статический метод в интерфейсе?

Нет, нельзя. С PHP 8.0 интерфейсы не могут содержать статические методы (deprecated в PHP 8.0, fatal error в будущих версиях).

Причина: статические методы не являются частью полиморфного контракта. Нельзя вызвать $interface::staticMethod() через переменную типа интерфейса.

Альтернативы: фабрика как обычный метод, именованные конструкторы как обычные методы, абстрактный класс со статическими методами.

Открыть отдельно →
39 Что такое Stringable interface?
// PHP 8.0: интерфейс Stringable
interface Stringable {
    public function __toString(): string;
}

// Любой класс с __toString() автоматически реализует Stringable
class Name {
    public function __construct(private string $value) {}
    public function __toString(): string { return $this->value; }
}

// Type hint
function greet(string|Stringable $name): string {
    return "Hello, $name";
}

greet('World');          // OK
greet(new Name('PHP'));  // OK

Stringable реализуется неявно - не нужно писать implements Stringable. Полезен для type hint когда функция принимает строку или объект, приводимый к строке.

Открыть отдельно →
40 Что такое alignment и padding?

В PHP alignment и padding не актуальны на уровне пользовательского кода - PHP абстрагирует управление памятью через zval-контейнеры.

В Go и C struct padding добавляется компилятором для выравнивания полей по границам слова процессора (4/8 байт). Неправильный порядок полей увеличивает размер структуры.

В PHP это имеет значение только при написании C-расширений (php-src) или при работе с pack()/unpack() для бинарных протоколов.

Открыть отдельно →
🧠Квиз 🏆Лидеры 🎯Собесед. 📖Вопросы 📚База зн.