Якщо ти готуєшся до senior PHP-співбесіди, цей гайд проведе тебе через теми, що виникають знову і знову — не базові речі, а хитрі нюанси, що відрізняють сильних кандидатів від середніх. Кожний розділ пояснює концепцію, показує невеликий приклад коду та вказує на пастки, де люди часто спотикаються.
Стаття написана зрозумілою мовою, щоб її було легко читати. Приклади коду несуть основне навантаження; текст навколо них лише додає контекст.

Зміст статті
- PHP Core та глибоке занурення в ООП — типи, інтерфейси, абстрактні класи, трейти, late static binding, нюанси поліморфізму
- Тонкощі системи типів — коваріантність та контраваріантність, неініціалізовані властивості, рівність проти ідентичності
- Сучасні можливості PHP — readonly, enum, генератори, магічні методи, анонімні функції
- Принципи SOLID на прикладах PHP
- Патерни проєктування, які слід знати
- Тестування в PHP — unit, integration, mock vs stub, принципи FIRST
- Основи безпеки — XSS, CSRF, SQL-ін'єкції, хешування проти шифрування
- HTTP, REST та API — методи, ідемпотентність, коди статусів
- Продуктивність та інструментарій — OPcache, Composer, Reflection, SPL
- Шпаргалка нових можливостей PHP 8.x
- Топ запитань для практики
1. PHP Core та глибоке занурення в ООП
1.1 Типи даних
PHP має 8 простих типів даних:
- 4 скалярні типи:
boolean,integer,float(такожdouble),string - 2 складені типи:
array,object - 2 спеціальні типи:
resource,NULL - Псевдотипи:
mixed,number,callable,void,never,iterable
1.2 Interface vs Abstract Class vs Trait
На перший погляд усі три схожі, але вони вирішують різні задачі.
Interface — це чистий контракт. Він містить лише сигнатури методів (та константи, починаючи з PHP 8.1). Клас може реалізовувати багато інтерфейсів.
Abstract class — це частково реалізований клас. Він може мати стан (властивості), реальні методи та конструктор. Клас може успадкувати лише один абстрактний клас.
Trait призначений для горизонтального повторного використання коду. Це не тип — ти не можеш оголосити тайп-хінт з trait. Вважай це «копіпастом на рівні компілятора».

interface PaymentGateway {
public function charge(Money $amount): TransactionId;
}
abstract class AbstractPaymentGateway implements PaymentGateway {
public function __construct(protected LoggerInterface $logger) {}
final public function charge(Money $amount): TransactionId {
$this->logger->info('Charging', ['amount' => $amount]);
return $this->doCharge($amount); // Template Method pattern
}
abstract protected function doCharge(Money $amount): TransactionId;
}
trait Timestampable {
private ?\DateTimeImmutable $createdAt = null;
public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; }
}
Коротко — «коли що обирати»:
| Потреба | Вибір | Приклад у цій статті |
|---|---|---|
| Чистий контракт, без логіки | Interface | PaymentGateway |
| Контракт плюс спільна логіка | Abstract class з Template Method | AbstractPaymentGateway |
| Невеликий технічний mix-in (таймстампи тощо) | Trait | Timestampable |
1.3 Late Static Binding (LSB або пізнє статичне зв'язування) — self vs static
self:: посилається на клас, де написаний код. static:: посилається на клас під час виконання. Це дуже важливо у фабричних методах.
class Model {
public static function create(): static {
return new static();
}
}
class User extends Model {}
$user = User::create(); // returns a User, NOT a Model
// If you replace `static` with `self`, it would always return a Model.
1.4 Приватні методи не перевизначаються
Це застає зненацька багатьох розробників.
class Base {
public function test(): string { return $this->doWork(); }
private function doWork(): string { return 'Base'; }
}
class Child extends Base {
private function doWork(): string { return 'Child'; }
}
echo (new Child())->test(); // outputs "Base"!
Приватні методи не беруть участі в поліморфізмі.
2. Тонкощі системи типів
2.1 Коваріантність та контраваріантність
Ці два слова звучать страшно, але ідея проста.
- Коваріантність (типи повернення): дочірній метод може повертати вужчий тип, ніж батьківський. Це дозволено.
- Контраваріантність (типи параметрів): дочірній метод повинен приймати той самий тип або ширший. Звуження параметрів заборонено, оскільки це порушує принцип підстановки Лісков.
class AnimalFeeder {
public function feed(Animal $a): Animal { return $a; }
}
class DogFeeder extends AnimalFeeder {
public function feed(Dog $d): Dog { return $d; } // FATAL ERROR!
// Narrowing the parameter from Animal to Dog is not allowed.
}
Правильна версія:
class DogFeeder extends AnimalFeeder {
public function feed(Animal $a): Dog { return $a; } // OK
}
2.2 Неініціалізовані типізовані властивості
Типізована властивість без значення за замовчуванням перебуває в особливому стані «неініціалізована» — це не те саме, що null.
class User {
public string $name; // uninitialized
}
$u = new User();
isset($u->name); // false
echo $u->name; // Error: must not be accessed before initialization
2.3 Рівність об'єктів проти ідентичності
$id1 = new UserId('abc');
$id2 = new UserId('abc');
$id1 == $id2; // true (compares properties)
$id1 === $id2; // false (different instances)
in_array($id2, [$id1]); // true (loose comparison)
in_array($id2, [$id1], true); // false (strict comparison)
Для Value Objects пишіть явний метод equals() замість того, щоб покладатися на ==.
3. Сучасні можливості PHP
3.1 Readonly — дещо оманлива назва
Readonly не означає «глибока незмінність». Воно лише захищає посилання на властивість, а не сам об'єкт усередині.
final class Order {
public function __construct(public readonly array $items) {}
}
$order = new Order([new Item('A'), new Item('B')]);
$order->items[] = new Item('C'); // ERROR — cannot reassign
$order->items[0]->name = 'Modified'; // OK! Mutating an inner object is allowed
У PHP 8.2 можна позначити весь клас як readonly, і тоді всі властивості автоматично стають readonly.
3.2 Enum (PHP 8.1)
Варіанти enum — це сінглтони. Вони не мають стану — лише константи та методи.
enum OrderStatus: string {
case Pending = 'pending';
case Paid = 'paid';
case Cancelled = 'cancelled';
public function canTransitionTo(self $new): bool {
return match ([$this, $new]) {
[self::Pending, self::Paid],
[self::Pending, self::Cancelled] => true,
default => false,
};
}
}
$s1 = OrderStatus::Paid;
$s2 = OrderStatus::from('paid');
var_dump($s1 === $s2); // true — same singleton
clone $s1; // ERROR — enums cannot be cloned
Backed enum має скалярне значення (рядок або ціле число). Pure enum його не має.
3.3 Генератори — одноразові
Генератор можна обійти лише один раз.
function readLines(string $file): \Generator {
$h = fopen($file, 'r');
while (($line = fgets($h)) !== false) {
yield trim($line);
}
fclose($h);
}
$lines = readLines('big.txt');
iterator_count($lines); // exhausts the generator
foreach ($lines as $line) {} // ERROR: closed generator
Якщо тобі потрібно пройтися по даних знову — або створи новий генератор, або перетвори його через iterator_to_array().
3.4 Магічні методи
Найпоширеніші, до обговорення яких слід бути готовим:
__construct()— ініціалізація, ін'єкція залежностей через конструктор__destruct()— викликається при звільненні посилань; порядок не гарантований__call()/__callStatic()— спрацьовують при виклику недоступного методу__get()/__set()— проксі Doctrine використовують їх для ледачого завантаження__invoke()— дозволяє використовувати об'єкт як функцію (використовується в Symfony у контролерах та Messenger-обробниках)__toString()— перетворює об'єкт на рядок; починаючи з PHP 8.0, це означає реалізаціюStringable__clone()— виконується після клонування об'єкта__sleep()/__wakeup()— використовуються під час serialize/unserialize__debugInfo()— налаштовує вивідvar_dump()
3.5 Анонімні функції
У PHP анонімна функція є об'єктом класу Closure. Через це вона трохи важча за звичайний об'єкт.
// Standard closure
$fn = function(int $x): int { return $x * 2; };
// Arrow function (short closure, since PHP 7.4)
$fn = fn(int $x): int => $x * 2;
// First-class callable syntax (PHP 8.1)
$fn = strlen(...);
3.6 Розв'язання конфліктів трейтів
Коли два трейти надають метод з однаковою назвою, PHP змушує тебе обрати переможця.
class Order {
use Loggable, Auditable {
Loggable::log insteadof Auditable;
Auditable::log as audit;
}
}
$order->log('hello'); // calls Loggable::log
$order->audit('hello'); // calls Auditable::log under a new name
Пріоритет, від найвищого до найнижчого: метод класу > метод трейту > метод батьківського класу.
3.7 Варіадики та іменовані аргументи — прихована пастка
function format(string $template, string ...$args): string {
return sprintf($template, ...$args);
}
format('%s %s', ...$parts); // OK
format(template: '%s %s', args: $parts); // ERROR: Unknown named parameter $args
4. Принципи SOLID на прикладах PHP
S — Single Responsibility (єдина відповідальність)
Клас повинен робити одну річ. UserRegistrationService не повинен водночас валідувати вхідні дані, хешувати паролі, надсилати листи і писати логи.
O — Open/Closed (відкритий/закритий)
Код повинен бути відкритим для розширення та закритим для модифікації. Додавання нового типу оплати означає новий клас, що реалізує PaymentGateway, а не редагування довгого блоку if/switch.
L — Liskov Substitution (підстановка Лісков)
Підкласи не повинні порушувати поведінку своїх батьків. Якщо SquareRepository кидає exception там, де батьківський клас ніколи цього не робив, — це порушення.
I — Interface Segregation (розподіл інтерфейсів)
Краще мати багато маленьких інтерфейсів, ніж один великий. Symfony сам розбиває контракти, пов'язані з користувачами, на UserInterface, PasswordAuthenticatedUserInterface, EquatableInterface тощо.
D — Dependency Inversion (інверсія залежностей)
Залежте від абстракцій, а не від конкретних класів. Контролер повинен залежати від UserRepositoryInterface, а не від DoctrineUserRepository.
// Domain layer — defines the contract
interface UserRepository {
public function findById(UserId $id): ?User;
public function save(User $user): void;
}
// Infrastructure layer — provides the implementation
final class DoctrineUserRepository implements UserRepository {
public function __construct(private EntityManagerInterface $em) {}
// ...
}
5. Патерни проєктування, які слід знати
Породжувальні (Creational)
- Factory Method — спільний інтерфейс для створення об'єктів, де підкласи вирішують конкретний тип.
- Abstract Factory — створює сімейства пов'язаних об'єктів без прив'язки до конкретних класів.
- Builder — будує складні об'єкти крок за кроком.
- Prototype — копіює об'єкти, не розкриваючи деталей їхнього створення.
- Singleton — єдиний спільний екземпляр з глобальним доступом.
Структурні (Structural)
- Adapter — з'єднує несумісні інтерфейси.
- Bridge — відокремлює абстракцію від реалізації.
- Composite — дозволяє працювати з деревом об'єктів як з одним.
- Decorator — додає поведінку під час виконання, огортаючи об'єкт.
- Facade — надає простий фасад для складної системи.
- Flyweight — ділить спільний стан між багатьма об'єктами.
- Proxy — об'єкт-замінник, що контролює доступ.
Поведінкові (Behavioral)
- Chain of Responsibility — передає запит по ланцюжку обробників.
- Command — обгортає запит як об'єкт.
- Iterator — обходить елементи по одному.
- Mediator — зменшує зв'язність, маршрутизуючи комунікацію через один об'єкт.
- Memento — зберігає та відновлює стан.
- Observer — pub/sub-сповіщення (основа
EventDispatcher). - State — змінює поведінку залежно від внутрішнього стану.
- Strategy — взаємозамінні алгоритми за одним інтерфейсом.
- Template Method — фіксований алгоритм з кроками, які підкласи можуть перевизначати.
- Visitor — додає нові операції без зміни класів, над якими вони виконуються.
Active Record vs Data Mapper
Класичне питання на співбесіді.
- Active Record (Laravel Eloquent): об'єкт є рядком таблиці і знає, як зберегти себе.
- Data Mapper (Doctrine): об'єкт нічого не знає про базу даних; збереженням займається окремий компонент.
Active Record зручний для невеликих проєктів. Data Mapper краще масштабується для складних доменів, оскільки модель залишається чистою.
6. Тестування в PHP
6.1 Рівні тестування
- Unit тести — тестують один компонент ізольовано, зазвичай з mock-ами або stub-ами.
- Integration тести — тестують групу компонентів разом; у Symfony це часто
KernelTestCaseз реальною базою даних. - Functional / system тести — тестують систему через публічний інтерфейс; у Symfony —
WebTestCaseзі справжніми HTTP-запитами.
6.2 Mock vs Stub
Ці два поняття часто плутають.
- Stub підставляє готові дані у систему, щоб тест міг виконатися.
- Mock перевіряє поведінку — він перевіряє, що певні методи були викликані з певними аргументами.
6.3 Принципи FIRST
Хороші тести дотримуються цих правил:
- Fast — тести виконуються швидко, тому розробники запускають їх часто.
- Independent — один тест не залежить від іншого.
- Repeatable — однаковий результат кожного разу, на будь-якій машині.
- Self-Validating — тест або проходить, або падає; не потрібно читати логи.
- Timely — написані разом з кодом (або до нього), а не через тижні.
6.4 Приклад структури тесту
final class RegisterUserHandlerTest extends TestCase {
public function testRegistersNewUser(): void {
$repo = $this->createMock(UserRepository::class);
$repo->expects($this->once())->method('save');
$handler = new RegisterUserHandler($repo);
$handler(new RegisterUserCommand('a@b.com', 'pwd'));
}
}
6.5 Трюк з годинником
Тестувати код, що використовує new \DateTimeImmutable(), болісно — ти не можеш підмінити поточний час. Рішення — ін'єкція годинника.
interface Clock {
public function now(): \DateTimeImmutable;
}
final class SystemClock implements Clock { /* real time */ }
final class FrozenClock implements Clock { /* fixed time for tests */ }
Це стандартизовано як PSR-20.
7. Основи безпеки
7.1 XSS (Cross-Site Scripting)
Зловмисники впроваджують JavaScript на твою сторінку. Захист:
- Екрануй вивід за допомогою
htmlentities()абоhtmlspecialchars(). - Налаштуй строгу Content Security Policy.
- Використовуй
HttpOnly-cookies, щоб JavaScript не міг читати токени сесії.
7.2 CSRF (Cross-Site Request Forgery)
Зловмисник змушує автентифікованого користувача виконувати дії, яких він не мав наміру виконувати. Захист: використовуй CSRF-токени для запитів, що змінюють стан.
7.3 SQL-ін'єкція
Дані від користувача потрапляють всередину SQL-запиту. Захист: завжди використовуй підготовлені вирази з прив'язкою параметрів. Doctrine та PDO роблять це за тебе, якщо використовувати їх правильно.
7.4 Кодування vs Шифрування vs Хешування
Три різні речі, які люди часто плутають:
- Кодування — для передачі (Base64, UTF-8). Будь-хто може його скасувати.
- Шифрування — для конфіденційності (AES, RSA). Двостороннє: можна розшифрувати, маючи ключ.
- Хешування — для цілісності та зберігання паролів (bcrypt, argon2). Одностороннє — скасувати неможливо.
8. HTTP, REST та API
8.1 HTTP-методи та ідемпотентність
Метод є ідемпотентним, якщо його багаторазовий виклик дає той самий результат, що й одноразовий.
| Метод | Призначення | Ідемпотентний | Має тіло у відповіді |
|---|---|---|---|
| GET | Отримати дані | Так | Так |
| POST | Створити | Ні | Так |
| PUT | Повна заміна | Так | Іноді |
| PATCH | Часткове оновлення | Ні* | Іноді |
| DELETE | Видалити | Так | Іноді |
| HEAD | Як GET, без тіла | Так | Ні |
| OPTIONS | Описати підтримку | Так | Так |
* PATCH можна спроєктувати ідемпотентним, але специфікація цього не гарантує.
8.2 Коди статусів
| Діапазон | Значення | Приклади |
|---|---|---|
| 1xx | Інформаційні | 100 Continue |
| 2xx | Успіх | 200 OK, 201 Created, 204 No Content |
| 3xx | Перенаправлення | 301 Moved Permanently, 302 Found, 304 Not Modified, 308 Permanent Redirect |
| 4xx | Помилка клієнта | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity |
| 5xx | Помилка сервера | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable |
8.3 Кращі практики REST
- Використовуй іменники для ресурсів (
/users,/posts), а не дієслова. - Використовуй множину для колекцій:
/users,/users/{id}. - Версіонуй API в URL:
/api/v1/.... - Фільтруй через параметри запиту:
?status=active&sort=name.
8.4 SOAP vs REST
SOAP — це строгий формат обміну повідомленнями (XML-конверти, стандарти WS-*). REST — гнучкий архітектурний стиль, який використовує HTTP як транспорт з JSON або XML-навантаженнями.
8.5 CORS
Браузери надсилають preflight OPTIONS-запит перед cross-origin викликом. Сервер відповідає заголовками Access-Control-Allow-Origin та Access-Control-Allow-Methods, щоб дозволити або заборонити доступ.
8.6 HTTP-кешування
Використовуйте заголовки Cache-Control, ETag та Last-Modified. Вони дозволяють клієнтам і проксі пропускати роботу, коли нічого не змінилося.
9. Продуктивність та інструментарій
9.1 OPcache
OPcache зберігає скомпільований PHP-байткод у пам'яті, тому рушій не парсить файли при кожному запиті. З preloading (PHP 7.4+) можна завантажити основні файли при старті та пропустити ще більше роботи. Обидва параметри мають бути увімкнені в production.
9.2 Composer та автозавантаження
Composer — це менеджер пакетів PHP. Він використовує PSR-4-автозавантаження: префікс простору імен відображається на директорію, а spl_autoload_register() виконує роботу з завантаження за лаштунками.
{
"autoload": {
"psr-4": { "App\\": "src/" }
}
}
9.3 Сесії vs Cookie
- Cookie зберігаються на клієнті, надсилаються з кожним запитом.
- Сесії зберігаються на сервері; клієнт тримає лише ідентифікатор сесії в cookie.
Сесії безпечніші для чутливих даних, оскільки браузер бачить лише непрозорий ідентифікатор.
9.4 Reflection API
Reflection дозволяє інспектувати класи, методи, властивості та параметри під час виконання. DI-контейнер Symfony використовує Reflection, щоб з'ясувати, що треба ін'єктувати — саме так працює autowiring.
9.5 SPL (Standard PHP Library)
Вбудований набір корисних компонентів:
- Структури даних:
SplStack,SplQueue,SplHeap,SplObjectStorage - Ітератори
- Стандартні виключення (
InvalidArgumentException,RuntimeExceptionтощо) spl_autoload_register()
9.6 RoadRunner
Сучасний PHP-сервер додатків, написаний на Go. Він тримає PHP-воркери живими між запитами, тому фреймворк не завантажується заново при кожному запиті. Значно прискорює Symfony та Laravel-додатки.
10. Шпаргалка нових можливостей PHP 8.x
Senior-розробник повинен вільно орієнтуватися у всьому з цього списку:
- Named arguments — виклик функцій за іменем параметра.
- Match expression — строга, expression-орієнтована альтернатива
switch. - Nullsafe operator (
?->) — розриває ланцюжок, коли щось єnull. - Constructor property promotion — оголошення та присвоєння властивостей у сигнатурі конструктора.
- Attributes (
#[Route],#[AsMessageHandler]) — нативні метадані замість анотацій у docblock. - Union types (8.0), intersection types (8.1), DNF types (8.2).
- Enums (8.1).
- Readonly properties (8.1) і readonly classes (8.2).
- First-class callable syntax (
strlen(...)) — 8.1. - Fibers (8.1) — примітиви для кооперативної багатозадачності.
11. Топ запитань для практики

Якщо ти можеш чітко відповісти на всі ці питання з прикладами — ти в хорошій формі:
- У чому різниця між інтерфейсом, абстрактним класом і трейтом? Коли використовувати кожен?
- У чому різниця між
selfіstatic, і що повертає фабричний метод? - Чому приватні методи не перевизначаються дочірніми класами? Передбач вивід невеликого прикладу.
- Поясніть коваріантність і контраваріантність. Чому звуження типу параметра заборонено?
- Чи робить
readonlyоб'єкт незмінним? Чи можна змінити щось усередині нього? - Що зробить PHP, якщо два трейти визначають однаковий метод?
- Чому генератор є одноразовим ітератором? Як обійти його двічі?
- У чому різниця між
==і===для об'єктів? - Що таке неініціалізована типізована властивість і чим вона відрізняється від
null? - Поясніть різницю між Active Record і Data Mapper.
- Коли
password_hash()потребує солі? (Підступне питання — вона генерується автоматично.) - Що таке OPcache і що preloading додає поверх нього?
- Як працює PSR-4-автозавантаження під капотом?
- У чому різниця між mock і stub?
- Які HTTP-методи є ідемпотентними і чому це важливо?
- Як захиститися від XSS, CSRF та SQL-ін'єкцій у PHP?
- Коли ти обрав би композицію замість успадкування? Наведи реальний приклад.
- Які нові можливості PHP 8.x змінили спосіб написання коду?
Підсумок
Senior PHP-співбесіди — це менше про заучування синтаксису і більше про розуміння того, чому речі працюють саме так. Наведені вище питання перевіряють саме це — вони досліджують межі мови, кордони твоїх абстракцій і те, як ти розмірковуєш про реальні системи.
Читай приклади коду, запускай їх самостійно, змінюй рядок і дивись, що зламається. Ця звичка навчить тебе більше, ніж будь-яка шпаргалка.
Успіху на співбесіді.






