Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

PHP: ООП

20 питань · ~20 хв · Версія v3.0

Увійдіть, щоб продовжити

Класи й видимість, успадкування й інтерфейси, трейти, магічні методи, readonly і хуки властивостей, пізнє статичне зв'язування - питання всіх рівнів, від junior до lead.

За спробу
20
У пулі
59
Проходжень
0
Середній бал
-
Пройшли на 70%+
-

Питання для підготовки

17 питань

Інтерфейс - це лише контракт: перелік публічних методів, які клас зобов'язується мати. Реалізації в ньому немає (крім констант), і клас може реалізувати скільки завгодно інтерфейсів.

Абстрактний клас - це недобудований клас: у ньому можуть бути властивості, готові методи й абстрактні методи, які мають дописати нащадки. Успадкувати можна лише один клас.

interface PaymentGateway
{
    public function charge(Money $amount): Receipt;
}

abstract class HttpGateway implements PaymentGateway
{
    public function __construct(protected HttpClient $http) {}

    protected function post(string $path, array $data): array
    {
        return $this->http->post($this->baseUrl() . $path, $data);
    }

    abstract protected function baseUrl(): string;
}

Коли що:

  • Інтерфейс - коли потрібна взаємозамінність: код залежить від PaymentGateway, а конкретний шлюз підставляється ззовні.
  • Абстрактний клас - коли кілька реалізацій ділять спільний код і стан.

Часто вони йдуть разом, як у прикладі: залежать від інтерфейсу, а абстрактний клас лише прибирає повтор між реалізаціями.

Докладніше в документації: Інтерфейси об'єктів

Це модифікатори видимості - хто може звертатися до властивості чи методу:

  • public - будь-хто, зокрема код поза класом.
  • protected - сам клас і його нащадки.
  • private - лише сам клас, де член оголошено. Нащадок його не бачить.
class Account
{
    public function __construct(private int $balance = 0) {}

    public function deposit(int $amount): void
    {
        $this->guard($amount);
        $this->balance += $amount;
    }

    protected function guard(int $amount): void
    {
        if ($amount <= 0) {
            throw new InvalidArgumentException('Сума має бути додатною');
        }
    }
}

Тут баланс не можна змінити напряму ззовні - лише через deposit(), який перевіряє суму. У цьому й сенс інкапсуляції: клас сам стежить за тим, щоб його стан лишався коректним.

Правило за замовчуванням: усе private, доки не з'явиться причина відкрити більше. Відкриту властивість потім важко закрити - на неї вже покладається чужий код.

У PHP 8.4 з'явилася асиметрична видимість: public private(set) int $balance - читати можна звідусіль, а змінювати лише всередині класу.

Докладніше в документації: Видимість

Трейт - це шматок коду (методи, властивості), який підставляється в клас так, ніби його написали там. Механізм повторного використання без успадкування: клас може взяти кілька трейтів.

trait HasSlug
{
    public function slug(): string
    {
        return Str::slug($this->title);
    }
}

class Post
{
    use HasSlug;
}

Підводні камені:

  • Неявні залежності. Трейт вище звертається до $this->title, але ніде цього не оголошує. Клас без title зламається лише під час виконання. Частково рятує абстрактний метод у трейті: abstract public function title(): string;.
  • Конфлікти імен. Два трейти з однаковим методом - фатальна помилка, доки не розв'язати її через insteadof і as.
  • Трейт - не тип. Не можна написати function f(HasSlug $x): перевірити, що об'єкт має поведінку трейту, можна лише через інтерфейс.
  • Прихована складність. Клас із п'ятьма трейтами важко читати: щоб зрозуміти, що в ньому є, треба відкрити шість файлів.

Добре працюють трейти для вузької допоміжної поведінки, як SoftDeletes чи HasFactory у Laravel. Погано - як спосіб розкидати велику модель по файлах: складність лишається, вона просто гірше видна.

Докладніше в документації: Трейти

self:: завжди вказує на клас, у якому написано код. static:: - на клас, через який метод викликали насправді. Друге називають пізнім статичним зв'язуванням.

class Model
{
    public static function create(): static
    {
        return new static();   // клас виклику
    }

    public static function make(): self
    {
        return new self();     // завжди Model
    }
}

class User extends Model {}

User::create(); // User
User::make();   // Model

Де це важливо:

  • Фабричні методи в базовому класі. Eloquent User::create() повертає саме User, бо всередині new static.
  • Перевизначені константи й методи. static::TABLE візьме константу нащадка, self::TABLE - базового класу.
  • Тип повернення static (PHP 8.0) каже аналізатору й IDE, що метод повертає клас виклику - зручно для fluent-інтерфейсів.

Коли self: коли поведінка не має змінюватися в нащадках, наприклад виклик приватного методу. static:: до приватного методу нащадка не дістанеться.

Докладніше в документації: Пізнє статичне зв'язування

Успадкування - найсильніший зв'язок між класами: нащадок залежить від усіх деталей предка, зокрема захищених. Будь-яка зміна в базовому класі може тихо зламати десяток нащадків - це називають проблемою крихкого базового класу.

Типові симптоми:

  • Ієрархія росте вглиб: Report → PdfReport → ColoredPdfReport → ... Нова комбінація ознак - новий клас.
  • Нащадок перевизначає метод, щоб «вимкнути» поведінку предка, яка йому не потрібна.
  • Базовий клас обростає protected-хелперами, бо так зручніше нащадкам.

Композиція - об'єкт отримує інші об'єкти і делегує їм роботу:

final class ReportExporter
{
    public function __construct(
        private Formatter $formatter,   // PDF, CSV, HTML
        private Storage $storage,       // диск, S3
    ) {}

    public function export(Report $report): string
    {
        return $this->storage->put($this->formatter->format($report));
    }
}

Тепер формат і сховище комбінуються вільно, кожне тестується окремо, і змінити одне можна, не чіпаючи іншого.

Успадкування доречне, коли є справжнє «є різновидом» і спільний контракт стабільний: власні винятки від RuntimeException, моделі від Model. Корисна звичка - робити класи final за замовчуванням: тоді успадкування стає свідомим рішенням, а не випадковістю.

Докладніше в документації: Успадкування об'єктів

Прочитати - ще не значить знати

20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.