Комп'ютер та ОС
Аналогія:уяви комп'ютер як кухню. Процесор (CPU) — це кухар, який виконує рецепти (інструкції). Оперативна пам'ять (RAM) — робочий стіл: на ньому лежать інгредієнти, з якими кухар працює зараз, але коли вимкнути світло (перезавантажити ПК), стіл прибирають — все зникає. Диск (SSD/HDD) — це холодильник і шафи: там все зберігається постійно, навіть якщо кухня закрита.
Операційна система (Windows, Linux, macOS) — це адміністратор кухні: вона розподіляє, хто з кухарів (програм) коли користується столом (RAM) і плитою (CPU), стежить за чергою і не дає двом кухарям вихопити один й той самий ніж одночасно.
Для розробника важливо розуміти: коли ти запускаєш PHP-скрипт, ОС виділяє йому шматок RAM, дає час CPU, і скрипт читає/пише файли на диску. Якщо скрипт "висить" — можливо, він чекає на диск або мережу, а не на CPU.
Технічніше: на реальному сервері PHP не має необмеженого доступу до RAM — у php.ini є директива memory_limit (типово 128M), і якщо скрипт спробує використати більше, PHP видасть Fatal error: Allowed memory size exhausted. У продакшн-середовищі зазвичай працює PHP-FPM: пул 'воркерів' (окремих процесів), кожен з яких обробляє один запит за раз — тому кількість RAM прямо обмежує, скільки запитів сервер витримає одночасно.
# Приклад: подивитись, скільки ресурсів використовує процес (Linux/Mac термінал)
top
# або конкретний процес
ps aux | grep php
# Скільки вільної памʼяті
free -h
Термінал та Linux
Аналогія:графічний інтерфейс (вікна, іконки) — це як меню в ресторані з картинками: зручно, але повільно і обмежено вибором, що є на фото. Термінал — це як прямо сказати кухарю: "звари 200г пасти, дуже al dente, без солі" — точно, швидко, і можна попросити те, чого немає на фото в меню.
Термінал — текстовий інтерфейс для команд ОС. У PHP-розробці термінал потрібен постійно: запустити скрипт, встановити пакет через Composer, подивитись логи, підключитись до сервера.
Основні команди, які варто знати напам'ять: pwd (де я зараз), ls (що тут є), cd (перейти в папку), mkdir (створити папку), rm (видалити — обережно!), cat (показати вміст файлу).
Технічніше: кожен файл і папка в Linux має права доступу (permissions), які видно через ls -l: наприклад -rwxr-xr-- означає, що власник може читати/писати/виконувати, група — тільки читати й виконувати, інші — тільки читати. Змінюють права командою chmod (наприклад chmod 755 script.sh). Символ | (pipe) передає вивід однієї команди на вхід іншої, наприклад cat file.log | grep error — знайти лише рядки з 'error' у файлі логів.
pwd # показати поточну директорію
ls -la # показати всі файли, включно з прихованими
cd projects # перейти в папку projects
mkdir my-course # створити нову папку
touch index.php # створити порожній файл
cat index.php # вивести вміст файлу в консоль
rm old-file.txt # видалити файл (без кошика!)
Як працює веб
Аналогія:відкриваючи сайт, ти — клієнт у ресторані (браузер), офіціант — це протокол HTTP, а кухня — сервер. Ти робиш замовлення (HTTP-запит: 'дай мені сторінку /menu'), офіціант несе його на кухню, кухня готує (сервер обробляє, часто через PHP), і офіціант приносить готову страву (HTTP-відповідь: HTML-сторінку).
Кожен запит має адресу (URL), метод (GET — 'дай мені', POST — 'ось дані від мене'), і відповідь має код статусу: 200 (все ок), 404 (сторінку не знайдено — 'такої страви немає в меню'), 500 (кухня зламалась — помилка сервера).
PHP якраз і живе 'на кухні' — це серверна мова: код виконується на сервері, а клієнту (браузеру) приходить вже готовий HTML.
Технічніше: окрім GET і POST, HTTP має ще методи PUT (повністю замінити ресурс), PATCH (частково оновити), DELETE (видалити) — разом вони формують основу REST API. Коди статусу групуються по першій цифрі: 2xx — успіх (200 OK, 201 Created), 3xx — перенаправлення (301 Moved Permanently), 4xx — помилка клієнта (400 Bad Request, 401 Unauthorized, 404 Not Found), 5xx — помилка сервера (500 Internal Server Error, 503 Service Unavailable).
<?php
// Простий приклад: сервер отримує запит і формує відповідь
// Файл index.php на сервері
header('Content-Type: text/html; charset=utf-8');
$name = $_GET['name'] ?? 'Гостю';
echo "<h1>Привіт, $name!</h1>";
// Якщо відкрити: site.com/index.php?name=Аліна
// Браузер отримає: <h1>Привіт, Аліна!</h1>
Мережі та інфраструктура
Аналогія:IP-адреса — це адреса будинку ('вулиця, номер'), а порт — номер квартири в цьому будинку. Домен (google.com) — це просто зручна вивіска замість запам'ятовування адреси, і DNS — це 'довідкове бюро', яке за назвою вивіски підкаже реальну адресу (IP).
Коли ти вводиш сайт.com у браузер: 1) браузер питає DNS 'яка IP-адреса у сайт.com?', 2) отримує IP, 3) стукає в двері (порт 80 для HTTP або 443 для HTTPS), 4) сервер відповідає.
HTTPS — це HTTP + шифрування (SSL/TLS): як лист у запечатаному конверті замість листівки, яку може прочитати кожен листоноша по дорозі.
Технічніше: є два основні транспортні протоколи: TCP (гарантує доставку і порядок пакетів — використовує HTTP, SSH) і UDP (швидший, без гарантій — використовує стрімінг, ігри). Файрвол — це 'охоронець на вході': він вирішує, які порти відкриті для зовнішніх з'єднань. Саме тому для backend-сервера на хостингу доводиться явно відкривати потрібні порти (наприклад 443 для HTTPS, 3306 для MySQL) у налаштуваннях файрвола.
# Перевірити, яка IP-адреса у домену
ping google.com
# Подивитись весь шлях пакету до сервера
traceroute google.com # Linux/Mac
tracert google.com # Windows
Git та VCS
Аналогія:Git — це машина часу для коду. Уяви, що пишеш курсову і зберігаєш 'курсова_v1.doc', 'курсова_final.doc', 'курсова_final_ТОЧНО.doc' — плутанина. Git замість цього робить 'знімки' (коміти) всього проєкту в певний момент, і ти завжди можеш повернутись до будь-якого знімку або порівняти, що змінилось.
GitHub — це як Google Drive для Git-проєктів: там зберігаються твої репозиторії онлайн, і кілька людей можуть працювати над одним кодом одночасно, кожен у своїй 'гілці' (branch), а потім зливати (merge) зміни докупи.
Базовий цикл роботи: git add (додати зміни у 'список на коміт'), git commit (зробити знімок з описом), git push (відправити знімки на GitHub).
Технічніше: файл .gitignore каже Git, які файли/папки ніколи не комітити (наприклад vendor/, .env з паролями, node_modules/). git diff показує точні рядки, які змінились, ще до коміту. git pull = git fetch (завантажити зміни з GitHub) + git merge (влити їх у твою гілку) — це два окремі кроки, об'єднані в одну зручну команду.
git init # почати новий репозиторій
git status # що змінилось
git add index.php # додати файл до коміту
git commit -m "Додав головну сторінку"
git push origin main # відправити на GitHub
git checkout -b feature/login # створити нову гілку
git log --oneline # історія комітів коротко
PhpStorm та IDE
Аналогія:писати код у Блокноті — як шити одяг голкою вручну в темряві. IDE (PhpStorm, VS Code) — це швейна машинка зі світлом: підказує помилки одразу (підкреслює червоним), автоматично довершує слова (автодоповнення), дозволяє миттєво перейти до визначення функції, і має вбудований дебагер — 'уповільнювач часу', який дозволяє зупинити виконання коду в певному рядку і подивитись, що відбувається всередині змінних.
Найкорисніші функції IDE для початківця: підсвітка синтаксису, автодоповнення (Ctrl+Space), перехід 'до визначення' (Ctrl+клік по функції), пошук по всьому проєкту (Shift+Shift у PhpStorm), і вбудований термінал.
Технічніше: PhpStorm інтегрується з Xdebug — розширенням PHP, яке і дає змогу реально зупиняти виконання на breakpoint і бачити стек викликів. Також є вбудована перевірка стилю коду (Code Sniffer / PSR-12), яка одразу підкреслює порушення стандартів жовтим, ще до того, як ти комітнеш код.
// Приклад того, що підказує IDE:
function calculateTotal(array $items): float {
// IDE підкаже помилку типу, якщо повернути не float
return array_sum($items);
}
// Автодоповнення підкаже методи класу після ->
$user = new User();
$user-> // тут IDE покаже список усіх доступних методів
Markdown та документація
Аналогія:Markdown — це як писати записку олівцем з простими позначками замість складного форматування у Word. Пишеш **жирний** і воно стає жирний — швидко і без мишки.
README.md у кожному GitHub-проєкті — це 'обкладинка книги', перше, що бачить людина: що за проєкт, як встановити, як запустити.
Технічніше: GitHub використовує розширену версію Markdown (GFM), яка додає таблиці (| Колонка 1 | Колонка 2 |), чекбокси завдань (- [ ] завдання / - [x] виконано) і автоматичні згадки issue/PR через #123.
# Заголовок проєкту
## Опис
Це навчальний PHP-проєкт.
## Встановлення
1. `composer install`
2. `php -S localhost:8000`
**Важливо:** потрібен PHP 8.1+
- пункт списку 1
- пункт списку 2
[Посилання на документацію](https://php.net)
```php
echo 'приклад коду в блоці';
```
Англійська для розробника
Аналогія:англійська в IT — як латина в медицині: практично вся документація, повідомлення про помилки і назви функцій написані нею. Не треба говорити вільно — треба вміти читати технічний текст і розуміти помилки.
Найважливіші слова, які варто знати напам'ять: undefined (не визначено), expected (очікувалось), argument (аргумент), deprecated (застаріле, скоро приберуть), exception (виключення/помилка), array, string, boolean, return, throw.
Технічніше: ще кілька термінів, які варто розрізняти: deprecated (застаріле, але поки працює) ≠ removed (вже видалено і викличе помилку); immutable (об'єкт, який не можна змінити після створення — як readonly-властивості); idempotent (операція, яку можна повторити багато разів з тим самим результатом — наприклад HTTP PUT, на відміну від POST).
// Типова помилка PHP англійською
Fatal error: Uncaught TypeError: strlen():
Argument #1 ($string) must be of type string,
array given
// Переклад: Помилка типу: strlen() очікує,
// що аргумент #1 буде рядком (string),
// а йому передали масив (array)
Soft skills та звички розробника
Аналогія:код читають набагато частіше, ніж пишуть — як книгу читають частіше, ніж редагують. Тому звичка писати зрозумілі назви змінних і невеликі функції — це подарунок собі-майбутній та колегам.
Ключові звички: 1) Гугли помилку перед тим, як питати — часто відповідь вже є на Stack Overflow. 2) Розбивай велику задачу на маленькі кроки (як у цьому курсі — одна тема за раз). 3) Не соромся питати, коли справді застрягла — краще 5 хвилин ніяковості, ніж 5 годин у глухому куті. 4) Коміть часто, маленькими шматками — легше знайти, де зламалось.
Технічніше: два принципи, які часто згадують у код-рев'ю: DRY (Don't Repeat Yourself — не дублюй один і той же код у кількох місцях) і KISS (Keep It Simple, Stupid — не ускладнюй рішення там, де можна простіше). Обидва прямо пов'язані з тим, чому важливі зрозумілі назви й невеликі функції.
// Погана звичка: незрозумілі назви
function f($a, $b) {
return $a * $b * 0.2;
}
// Хороша звичка: зрозумілі назви = код сам себе документує
function calculateDiscount(float $price, int $quantity): float {
$discountRate = 0.2;
return $price * $quantity * $discountRate;
}
Встановлення та оточення
Аналогія:PHP-інтерпретатор — це перекладач, який стоїть на сервері і перекладає твій код (.php файл) на дії, зрозумілі комп'ютеру. Без встановленого PHP файл .php — просто текст.
Найпростіший спосіб для розробки backend локально — вбудований сервер PHP: одна команда, і твій API вже доступний на localhost, без потреби налаштовувати Apache/Nginx.
SAPI (Server API): PHP може працювати в різних режимах — CLI (командний рядок, php script.php), вбудований dev-сервер, або PHP-FPM (для продакшн-серверів з Nginx). Кожен SAPI може мати трохи різні налаштування php.ini.
Ключові директиви php.ini для розробки: display_errors = On і error_reporting = E_ALL показують усі помилки прямо в консолі чи браузері (в продакшені їх вимикають, а помилки пишуть у лог-файл, щоб не показувати внутрішні деталі стороннім). memory_limit обмежує, скільки RAM може використати один скрипт.
# Перевірити версію PHP
php -v
# Запустити вбудований dev-сервер API
php -S localhost:8000
# Виконати файл напряму без сервера (CLI SAPI)
php script.php
# Подивитись, який php.ini реально використовується
php --ini
Синтаксис та типи
Аналогія:змінна — це підписана коробка. $price = 1899.00 означає 'постав у коробку з ярликом price значення 1899.00'. PHP — мова з динамічною типізацією.
Основні типи: int, float, string, bool, array, null. З PHP 7+ можна вказувати типи явно (type hinting).
Type juggling (жонглювання типами): без strict_types=1 PHP автоматично приводить типи одне до одного: '5' + 3 дасть 8, функція, що очікує int, мовчки прийме рядок '5'. Це джерело неочевидних багів у продакшн-коді — тому в сучасному PHP майже завжди варто вмикати declare(strict_types=1); на початку файлу.
Порівняння == vs ===: == порівнює значення ПІСЛЯ приведення типів (нестроге), === порівнює і значення, і тип (строге). Класичний приклад проблеми: до PHP 8 вираз 0 == 'abc' повертав true. Завжди використовуй ===, якщо немає конкретної причини для ==.
<?php
declare(strict_types=1);
function calculateTotal(float $price, int $quantity, bool $hasDiscount): float
{
$total = $price * $quantity;
if ($hasDiscount) {
$total *= 0.9;
}
return round($total, 2);
}
$sku = 'SKU-1001'; // string
$price = 1899.00; // float
$quantity = 2; // int
$inStock = true; // bool
$tags = ['keyboard', 'mechanical']; // array
$discountCode = null; // null
echo calculateTotal($price, $quantity, true); // 3418.2
var_dump(5 === '5'); // false (різні типи)
var_dump(5 == '5'); // true (нестроге порівняння)
Керуючі конструкції
Аналогія:if/else — розвилка: 'якщо роль адмін — дозволь видалення, інакше — заборони'. Цикли (for, while, foreach) — конвеєр: повторюй дію, поки виконується умова.
for — коли знаєш точну кількість повторів. while — коли повторюєш, поки умова істинна. foreach — для перебору масивів, найчастіше використовується в backend для обробки результатів запиту до БД.
match (з PHP 8) — сучасніша й безпечніша заміна switch: суворо порівнює значення (як ===) і одразу повертає результат без 'провалювання' між case.
Оператор ?? (null coalescing): $role = $request['role'] ?? 'guest'; — 'взяти значення, якщо воно існує і не null, інакше — значення за замовчуванням'. Це найпоширеніший спосіб обробки необов'язкових параметрів API-запиту. Скорочений тернарний ?: перевіряє на falsy (0, '', false), а не саме на null.
<?php
declare(strict_types=1);
function checkAccess(string $role): bool
{
return match($role) {
'admin', 'manager' => true,
'customer' => false,
default => false,
};
}
$request = ['user_id' => 42];
$role = $request['role'] ?? 'guest'; // немає ключа 'role' -- беремо 'guest'
if (checkAccess($role)) {
echo "Доступ дозволено\n";
} else {
echo "Доступ заборонено, роль: $role\n";
}
// foreach -- типова обробка результатів запиту до БД
$orders = [
['id' => 1, 'status' => 'paid'],
['id' => 2, 'status' => 'pending'],
];
foreach ($orders as $order) {
echo "Замовлення #{$order['id']}: {$order['status']}\n";
}
Масиви та колекції
Аналогія:масив — це шафа з шухлядками. У backend результат запиту до бази даних майже завжди приходить саме масивом асоціативних масивів — кожен елемент представляє один рядок таблиці.
Найкорисніші функції: array_map (застосувати функцію до кожного елемента), array_filter (залишити тільки те, що підходить під умову), array_reduce (звести весь масив до одного значення).
array_merge vs оператор +: array_merge($a, $b) перезаписує однакові текстові ключі значеннями з другого масиву і переіндексовує числові ключі. Оператор $a + $b залишає значення з першого масиву, якщо ключ уже є в ньому — це не одне й те саме, і плутанина тут -- часте джерело багів.
Деструктуризація: ['id' => $id, 'price' => $price] = $product; дозволяє одразу 'розпакувати' асоціативний масив у окремі змінні -- зручно для обробки одного запису з результату запиту.
<?php
declare(strict_types=1);
$products = [
['sku' => 'SKU-1001', 'price' => 1899.00, 'inStock' => true],
['sku' => 'SKU-2005', 'price' => 350.00, 'inStock' => false],
['sku' => 'SKU-3010', 'price' => 799.00, 'inStock' => true],
];
// array_filter: тільки товари в наявності
$available = array_filter($products, fn($p) => $p['inStock']);
// array_map: витягнути тільки ціни
$prices = array_map(fn($p) => $p['price'], $available);
// array_reduce: сума всіх цін товарів у наявності
$total = array_reduce($prices, fn($carry, $p) => $carry + $p, 0.0);
echo count($available); // 2
print_r($prices);
echo $total; // 2698
// Деструктуризація одного запису
['sku' => $sku, 'price' => $price] = $products[0];
echo "$sku коштує $price грн\n";
Рядки та Regex
Аналогія:рядок — це нитка з літер. Функції роботи з рядками — ножиці й голка: обрізати (substr), склеїти (implode), знайти шматок (strpos), замінити (str_replace).
Регулярні вирази (regex) — потужніший 'пошуковий шаблон', незамінний для валідації вхідних даних API (email, телефон, SKU) і складного пошуку/заміни.
Форматований вивід: sprintf('Ціна: %.2f грн', $price) повертає рядок за шаблоном — зручно для формування текстових повідомлень API-відповіді чи логів.
Multibyte-функції: якщо працюєш з кирилицею чи іншими не-ASCII символами, звичайні strlen/substr рахують БАЙТИ, а не символи, і можуть 'розрізати' UTF-8 символ навпіл. Для коректної роботи з такими рядками є mb_-функції (mb_strlen, mb_substr).
<?php
declare(strict_types=1);
$sku = ' sku-1001 ';
$cleanSku = strtoupper(trim($sku)); // 'SKU-1001'
// Regex: валідація email при реєстрації
$email = 'client@shop.com';
if (preg_match('/^[\w.+-]+@[\w-]+\.[a-z]{2,}$/i', $email)) {
echo "Email валідний\n";
}
// Regex: перевірка формату SKU (3 літери - 4 цифри)
$validSkuPattern = '/^[A-Z]{3}-\d{4}$/';
var_dump(preg_match($validSkuPattern, 'SKU-1001')); // 1 (валідний)
var_dump(preg_match($validSkuPattern, 'sku1001')); // 0 (невалідний)
// sprintf для форматованого API-повідомлення
$message = sprintf('Товар %s додано до кошика, ціна: %.2f грн', $cleanSku, 1899.0);
echo $message;
Функції та замикання
Аналогія:функція — це багаторазовий інструмент: даєш йому вхідні дані (аргументи), він щось робить, повертає результат. Один раз написав функцію calculateTax() — використовуєш її в будь-якому місці backend-коду.
Анонімна функція (closure) — 'одноразовий' інструмент без назви, часто передається як аргумент іншій функції (наприклад у array_map). use ($var) дозволяє замиканню 'запам'ятати' зовнішню змінну.
Стрілочні функції fn($x) => $x * 2 — коротший запис closure, автоматично 'бачать' зовнішні змінні без явного use().
Variadic-функції: function sum(int ...$nums) приймає будь-яку кількість аргументів, зібраних у масив. First-class callable syntax (PHP 8.1+): замість 'strlen' чи [$obj, 'method'] можна писати strlen(...) — створює Closure напряму з існуючої функції.
<?php
declare(strict_types=1);
function calculateTax(float $amount, float $rate = 0.2): float
{
return round($amount * $rate, 2);
}
// Closure з use() -- 'запам'ятовує' зовнішню знижку
$discountRate = 0.1;
$applyDiscount = function(float $price) use ($discountRate): float {
return round($price * (1 - $discountRate), 2);
};
echo $applyDiscount(1899.00); // 1709.1
// Стрілочна функція -- автоматично 'бачить' зовнішні змінні
$shippingCost = 50.0;
$withShipping = fn($total) => $total + $shippingCost;
echo $withShipping(1899.00); // 1949
// Variadic: сума будь-якої кількості товарів у кошику
function cartTotal(float ...$prices): float
{
return array_sum($prices);
}
echo cartTotal(1899.00, 350.00, 799.00); // 3048
// First-class callable syntax (PHP 8.1+)
$mapper = strtoupper(...);
echo $mapper('sku-1001'); // SKU-1001
Файли та формати (JSON, CSV)
Аналогія:файл на диску — аркуш паперу в шафі. file_get_contents — 'дістати й прочитати', file_put_contents — 'записати і покласти назад'.
JSON — універсальний формат для обміну структурованими даними між системами. PHP-масив ↔ JSON конвертується через json_encode / json_decode. Це саме той формат, у якому реальні API повертають відповіді клієнту.
Формування коректної API-відповіді: header('Content-Type: application/json') + http_response_code($code) + echo json_encode($data) — це стандартний 'скелет' відповіді бекенду будь-якого REST API, написаного на чистому PHP.
Безпечна робота з файлами: перед читанням файлу варто перевірити його існування (file_exists()), а для великих файлів рядок за рядком краще використовувати fopen() + fgets() у циклі, щоб не завантажувати весь файл у пам'ять одразу.
<?php
declare(strict_types=1);
function jsonResponse(array $data, int $statusCode = 200): void
{
http_response_code($statusCode);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
$products = [
['sku' => 'SKU-1001', 'price' => 1899.00],
['sku' => 'SKU-2005', 'price' => 350.00],
];
// Зберегти 'кеш' товарів у JSON-файл
file_put_contents('products_cache.json', json_encode($products));
// Прочитати назад
$cached = json_decode(file_get_contents('products_cache.json'), true);
print_r($cached);
// Типова API-відповідь
jsonResponse(['products' => $products, 'count' => count($products)]);
// CSV-експорт замовлень
$fp = fopen('orders_export.csv', 'w');
fputcsv($fp, ['order_id', 'total']);
fputcsv($fp, [4521, 1899.00]);
fclose($fp);
Обробка помилок
Аналогія:try/catch — страхувальна сітка. 'Спробуй (try) виконати запит до бази даних; якщо стався збій (виключення) — сітка (catch) тебе спіймає', і API поверне зрозумілу помилку клієнту, а не 'впаде' повністю (500 Internal Server Error без пояснень).
Ієрархія Throwable (офіційно, php.net): усі помилки й виключення в PHP реалізують інтерфейс Throwable, який розділяється на дві гілки: Error (внутрішні помилки PHP -- TypeError, DivisionByZeroError) і Exception (виключення, які кидає код розробника, наприклад InvalidArgumentException, RuntimeException). Можна перехопити обидві гілки одним catch (Throwable $e).
finally виконується завжди — і при успіху, і при помилці (наприклад, щоб гарантовано закрити з'єднання з базою даних незалежно від результату).
Типова backend-практика: у контролері API перехоплюють виключення і повертають клієнту зрозумілий JSON з кодом статусу (404, 400, 500), а не 'сирий' текст помилки PHP, який може розкрити внутрішню структуру системи.
<?php
declare(strict_types=1);
class OrderNotFoundException extends RuntimeException {}
function fetchOrderById(int $id, array $database): array
{
if (!isset($database[$id])) {
throw new OrderNotFoundException("Замовлення #$id не знайдено");
}
return $database[$id];
}
function jsonResponse(array $data, int $status = 200): void
{
http_response_code($status);
echo json_encode($data);
}
$database = [4521 => ['id' => 4521, 'total' => 1899.00]];
try {
$order = fetchOrderById(9999, $database);
jsonResponse(['order' => $order]);
} catch (OrderNotFoundException $e) {
jsonResponse(['error' => $e->getMessage()], 404);
} catch (Throwable $e) {
jsonResponse(['error' => 'Внутрішня помилка сервера'], 500);
} finally {
echo "\n--- Запит оброблено ---";
}
Composer та пакети
Аналогія:Composer — менеджер продуктів для кухні backend-проєкту: замість самій писати логіку роботи з датами чи JWT-токенами з нуля, замовляєш готовий, перевірений пакет із каталогу Packagist.
composer.json — список 'покупок' (залежностей), composer.lock — точний чек з версіями, які реально встановлені у ВСІХ розробників команди і на сервері однаково.
Версійні обмеження: ^2.5 означає 'будь-яка версія від 2.5 до наступної мажорної (3.0 виключно)' (сумісні зміни), ~2.5 — вужче, тільки патч-версії в межах 2.5.x.
Автозавантаження (PSR-4 autoload): замість ручного require кожного файлу, PHP сам знаходить потрібний клас за неймспейсом через require 'vendor/autoload.php' — цей файл Composer генерує автоматично при кожному встановленні пакета.
# Ініціалізувати проєкт backend API
composer init
# Встановити пакет для роботи з датами
composer require nesbot/carbon
# Встановити пакет для JWT-токенів авторизації
composer require firebase/php-jwt
# Встановити пакет тільки для розробки (тести)
composer require --dev phpunit/phpunit
PSR-стандарти
Аналогія:PSR (PHP Standards Recommendations) — єдиний стандарт розеток: якщо всі розробники дотримуються одного стилю коду, будь-яка бібліотека 'підійде' до будь-якого проєкту без 'перехідників'.
PSR-1 / PSR-12 — стандарти стилю коду: класи — PascalCase, методи — camelCase, відступ — 4 пробіли, файли з класами не повинні мати 'побічних ефектів' (echo, вивід html) при простому підключенні — тільки оголошення.
PSR-4 — стандарт автозавантаження: неймспейс класу відповідає реальному шляху файлу. Якщо в composer.json прописано "App\\": "src/", то клас App\Controllers\ProductController Composer шукатиме у файлі src/Controllers/ProductController.php.
Дотримання PSR робить код зрозумілим для будь-якого іншого PHP-розробника і критично важливе, коли над одним backend-проєктом працює команда.
<?php
declare(strict_types=1);
namespace App\Controllers;
use App\Repositories\ProductRepository;
// PSR-1/12: клас -- PascalCase, файл = ім'я класу (ProductController.php)
class ProductController
{
public function __construct(private readonly ProductRepository $repository) {}
// PSR-12: метод -- camelCase, відступ 4 пробіли
public function index(): array
{
$products = $this->repository->all();
return [
'status' => 'ok',
'products' => $products,
];
}
public function show(int $id): array
{
$product = $this->repository->find($id);
return $product !== null
? ['status' => 'ok', 'product' => $product]
: ['status' => 'error', 'message' => 'Product not found'];
}
}
Класи, об'єкти, конструктори, readonly
Аналогія:клас — це креслення (blueprint) товару на складі: воно описує, які поля обов'язкові (назва, ціна, SKU) і що з товаром можна робити. Об'єкт — це конкретний товар на полиці, створений за цим кресленням. Кожен товар має власні значення (своя назва, своя ціна), хоча структура в усіх однакова.
Що таке клас формально: клас — це визначення типу даних, що об'єднує стан (властивості) і поведінку (методи) в одну сутність. Створення об'єкта — інстанціювання — відбувається через new ClassName(...), і в цей момент PHP автоматично викликає метод __construct(), якщо він визначений.
Constructor Promotion (з PHP 8.0): замість того, щоб окремо оголошувати властивість і окремо присвоювати її в тілі конструктора, можна оголосити властивість прямо в списку параметрів конструктора, вказавши модифікатор доступу. Це не 'магія' — компілятор PHP просто розгортає це в звичайне оголошення властивості + присвоєння, це чистий синтаксичний цукор, який економить рядки коду і зменшує дублювання назви поля.
readonly (з PHP 8.1): властивість, позначена readonly, обов'язково повинна мати вказаний тип, не може мати значення за замовчуванням у оголошенні, і присвоїти їй значення можна рівно один раз — з коду всередині того самого класу (найчастіше в конструкторі). Будь-яка повторна спроба запису викличе Error: Cannot modify readonly property. Це ідеально підходить для DTO (Data Transfer Object) і Value Object — сутностей, які логічно не повинні змінюватись після створення, наприклад товар з фіксованим SKU чи сума платежу.
Типова помилка початківців: забувати вказувати тип для readonly-властивості (readonly $price без типу — фатальна помилка компіляції) або намагатись задати значення за замовчуванням (public readonly float $price = 0.0 — теж помилка, бо це вже вважається 'записом').
<?php
declare(strict_types=1);
final class Product
{
public function __construct(
public readonly string $sku,
public readonly string $name,
private float $price,
) {}
public function getPrice(): float
{
return $this->price;
}
public function applyDiscount(float $percent): void
{
$this->price = round($this->price * (1 - $percent / 100), 2);
}
}
$product = new Product('SKU-1001', 'Механічна клавіатура', 1899.00);
echo $product->name;
echo $product->getPrice();
$product->applyDiscount(10);
echo $product->getPrice();
// $product->sku = 'SKU-9999'; // Fatal error: Cannot modify readonly property
Модифікатори доступу та інкапсуляція
Аналогія:уяви кошик покупок (shopping cart) в інтернет-магазині. Ти не можеш напряму 'залізти рукою' і виправити цифру суми замовлення в базі даних — ти можеш тільки викликати дії, які магазин дозволяє: 'додати товар', 'видалити товар', 'застосувати промокод'. Внутрішня логіка підрахунку суми — це приватна деталь, схована від зовнішнього світу.
Три рівні видимості: public — доступно звідусіль. protected — доступно всередині класу і всіх його нащадків, але не ззовні. private — доступно ТІЛЬКИ всередині того самого класу, де оголошено.
Навіщо взагалі ховати дані: якщо властивість $total кошика публічна, будь-який код ззовні може встановити $cart->total = -500, зламавши бізнес-логіку. Якщо ж total обчислюється приватним методом на основі приватного масиву товарів, зовнішній код фізично не може поставити його в некоректний стан.
Геттери й сеттери: це публічні методи, що дають контрольований доступ до приватних даних. Хороший сеттер не просто присвоює значення, а перевіряє його коректність.
Типова помилка: робити геттери й сеттери для КОЖНОЇ приватної властивості 'про всяк випадок' — це фактично робить приватність марною. Інкапсуляція працює, тільки коли сеттер реально щось перевіряє або обчислює.
<?php
declare(strict_types=1);
class ShoppingCart
{
private array $items = [];
public function addItem(string $sku, int $quantity): void
{
if ($quantity <= 0) {
throw new InvalidArgumentException('Кількість повинна бути додатньою');
}
$this->items[$sku] = ($this->items[$sku] ?? 0) + $quantity;
}
public function removeItem(string $sku): void
{
unset($this->items[$sku]);
}
public function getItemCount(): int
{
return array_sum($this->items);
}
public function getItems(): array
{
return $this->items;
}
}
$cart = new ShoppingCart();
$cart->addItem('SKU-1001', 2);
$cart->addItem('SKU-2005', 1);
echo $cart->getItemCount();
// $cart->items = []; // Помилка! items -- private
Наслідування
Аналогія:у системі сповіщень інтернет-магазину є спільна логіка для будь-якого типу сповіщення (сформувати текст, залогувати відправку), але конкретний спосіб доставки різний: email, SMS чи push. Наслідування дозволяє один раз описати спільне в батьківському класі, а кожен конкретний канал — тільки те, чим він унікальний.
Синтаксис: class EmailNotification extends Notification означає, що EmailNotification автоматично отримує всі public/protected властивості й методи класу Notification. parent::method() дозволяє викликати оригінальну версію методу зсередини перевизначеного методу дочірнього класу.
Перевизначення (override): дочірній клас може оголосити метод з тим самим ім'ям — PHP використає версію дочірнього класу. Сигнатура перевизначеного методу повинна бути сумісною з батьківською, інакше PHP видасть помилку 'Declaration must be compatible'.
Обмеження: PHP підтримує лише одиночне наслідування класів. Якщо потрібна поведінка з кількох незалежних джерел, використовують інтерфейси або трейти.
Типова помилка: будувати занадто глибокі ланцюжки наслідування — це ускладнює розуміння коду. Правило 'composition over inheritance' часто рекомендує композицію замість глибокого extends.
<?php
declare(strict_types=1);
abstract class Notification
{
public function __construct(protected string $recipient, protected string $message) {}
public function log(): void
{
echo "[LOG] Надсилаємо сповіщення до {$this->recipient}\n";
}
abstract public function send(): bool;
}
class EmailNotification extends Notification
{
public function send(): bool
{
$this->log();
echo "Email до {$this->recipient}: {$this->message}\n";
return true;
}
}
class SmsNotification extends Notification
{
public function __construct(string $recipient, string $message, private int $maxLength = 160)
{
parent::__construct($recipient, $message);
}
public function send(): bool
{
parent::log();
$text = mb_substr($this->message, 0, $this->maxLength);
echo "SMS до {$this->recipient}: $text\n";
return true;
}
}
$email = new EmailNotification('client@shop.com', 'Ваше замовлення #4521 оплачено');
$email->send();
Абстрактні класи та інтерфейси
Аналогія:у backend інтернет-магазину потрібно приймати оплату різними способами, але код, що ОБРОБЛЯЄ замовлення, не повинен знати конкретних деталей кожного платіжного провайдера — йому важливо тільки, що будь-який спосіб оплати вміє 'списати суму'. Інтерфейс — це формальний контракт без деталей реалізації.
Інтерфейс формально: оголошується через interface Name { public function method(): type; } — містить ТІЛЬКИ сигнатури методів. Клас 'підписується' під контракт через implements, і зобов'язаний реалізувати абсолютно всі методи — інакше PHP видасть фатальну помилку ще на етапі оголошення класу. Один клас може реалізовувати кілька інтерфейсів одночасно.
Абстрактний клас формально: оголошується через abstract class Name, може містити і звичайні (реалізовані), і абстрактні методи. Не можна створити об'єкт абстрактного класу напряму.
Коли що обирати: інтерфейс — коли класи логічно не пов'язані спільним походженням, але дотримуються одного контракту. Абстрактний клас — коли є і спільний контракт, і спільна готова логіка.
Типова помилка: плутати мету інтерфейсу з мутою 'просто зробити менше коду'. Якщо потрібна спільна логіка — це абстрактний клас або трейт.
<?php
declare(strict_types=1);
interface PaymentGateway
{
public function charge(float $amount): bool;
}
abstract class BasePaymentGateway implements PaymentGateway
{
protected function validateAmount(float $amount): void
{
if ($amount <= 0) {
throw new InvalidArgumentException('Сума платежу повинна бути додатньою');
}
}
abstract public function charge(float $amount): bool;
}
class StripeGateway extends BasePaymentGateway
{
public function charge(float $amount): bool
{
$this->validateAmount($amount);
echo "Stripe: списано $amount грн\n";
return true;
}
}
class CashOnDeliveryGateway extends BasePaymentGateway
{
public function charge(float $amount): bool
{
$this->validateAmount($amount);
echo "Готівка при отриманні: очікується $amount грн\n";
return true;
}
}
function processOrder(PaymentGateway $gateway, float $total): void
{
$gateway->charge($total);
}
processOrder(new StripeGateway(), 1899.00);
// new BasePaymentGateway(); // Помилка! Не можна створити об'єкт абстрактного класу
Трейти (Traits)
Аналогія:у backend-проєкті логування потрібне і класу Order, і класу Product, і класу User — але вони жодним чином не пов'язані спільним батьківським класом. Трейт — це 'домішка' готової поведінки, яку можна підключити в будь-який клас незалежно від його місця в ієрархії наслідування.
Синтаксис: trait Loggable { public function log(string $msg): void {...} }, підключення — class Order { use Loggable; }. Трейт не можна інстанціювати самостійно.
Вирішення конфліктів (офіційний синтаксис php.net): якщо два трейти визначають метод з однаковою назвою, PHP видасть фатальну помилку, доки конфлікт не вирішено явно через insteadof (обрати конкретну версію) і as (дати іншому методу нове ім'я замість втрати доступу до нього).
Трейт vs абстрактний клас: трейт НЕ бере участі в перевірці типів ($order instanceof Loggable поверне false, якщо Loggable — трейт) — це головна відмінність. Трейт — суто механізм повторного використання коду, а не контракт типів.
Типова помилка: зловживати великою кількістю трейтів в одному класі 'про запас'. Розумний ліміт — 1-3 добре названі трейти на клас.
<?php
declare(strict_types=1);
trait Loggable
{
public function log(string $message): void
{
echo '[' . static::class . "] $message\n";
}
}
trait Timestampable
{
private ?string $createdAt = null;
public function markCreated(): void
{
$this->createdAt = date('Y-m-d H:i:s');
}
public function getCreatedAt(): ?string
{
return $this->createdAt;
}
}
class Order
{
use Loggable, Timestampable;
public function __construct(public readonly int $id) {}
}
class Product
{
use Loggable;
}
$order = new Order(4521);
$order->markCreated();
$order->log('Замовлення створено');
echo $order->getCreatedAt();
$product = new Product();
$product->log('Товар додано на склад');
Статичні члени та константи класу
Аналогія:номер замовлення в інтернет-магазині повинен бути унікальним і послідовним для ВСІХ замовлень системи — це не властивість одного замовлення, а спільний лічильник, що належить самому класу Order.
Статичні властивості й методи: оголошуються з static, належать класу в цілому — усі екземпляри 'бачать' одне значення. Доступ — через ім'я класу і ::, без потреби створювати об'єкт.
self:: проти static:: (пізнє статичне зв'язування): self:: завжди звертається до класу, де НАПИСАНИЙ код. static:: звертається до класу, який РЕАЛЬНО викликається під час виконання. Різниця критична, коли дочірній клас перевизначає статичний метод.
Константи класу: public const NAME = value;, доступ — ClassName::NAME. На відміну від static-властивостей, константи не можна змінити після оголошення.
Типова помилка: плутати статичну властивість із 'спільною за замовчуванням, але кожен об'єкт може мати свою'. Static-властивість справді ОДНА на весь клас.
<?php
declare(strict_types=1);
class Order
{
public const MIN_AMOUNT = 50.0;
public const MAX_AMOUNT = 100000.0;
private static int $totalCreated = 0;
private int $orderNumber;
public function __construct(private readonly float $amount)
{
if ($amount < self::MIN_AMOUNT || $amount > self::MAX_AMOUNT) {
throw new InvalidArgumentException('Сума поза дозволеним діапазоном');
}
self::$totalCreated++;
$this->orderNumber = 4500 + self::$totalCreated;
}
public function getOrderNumber(): int
{
return $this->orderNumber;
}
public static function getTotalCreated(): int
{
return self::$totalCreated;
}
}
$order1 = new Order(1899.00);
$order2 = new Order(350.00);
echo $order1->getOrderNumber();
echo $order2->getOrderNumber();
echo Order::getTotalCreated();
echo Order::MIN_AMOUNT;
Поліморфізм
Аналогія:у backend оплати підключено кілька платіжних провайдерів. Код, що обробляє замовлення, викликає $gateway->charge($amount) для будь-якого з них ОДНАКОВО — не перевіряючи спочатку 'а це часом не Stripe?'. Кожен провайдер сам знає, як саме списати кошти.
Формальне визначення: поліморфізм — здатність різних класів, що реалізують один інтерфейс, мати власну реалізацію одного й того ж методу-контракту, при цьому виклик коду залишається однаковим.
Принцип підстановки Лісков (Liskov Substitution Principle, 'L' у SOLID): дочірній клас повинен можна використати всюди, де очікується базовий тип, не ламаючи коректність програми.
Як це працює технічно: коли пишеш function processOrder(PaymentGateway $gateway), рішення про те, ЯКИЙ саме метод charge() виконати, приймається динамічно під час виконання, на основі реального класу об'єкта.
Типова помилка: писати if ($gateway instanceof StripeGateway) {...} elseif (...) замість того, щоб покластись на поліморфізм. Такий код ламається щоразу, коли додається новий шлюз.
<?php
declare(strict_types=1);
interface PaymentGateway
{
public function charge(float $amount): bool;
}
class StripeGateway implements PaymentGateway
{
public function charge(float $amount): bool
{
echo "Stripe: списано $amount грн через картку\n";
return true;
}
}
class PayPalGateway implements PaymentGateway
{
public function charge(float $amount): bool
{
echo "PayPal: перевід $amount грн\n";
return true;
}
}
class CashOnDeliveryGateway implements PaymentGateway
{
public function charge(float $amount): bool
{
echo "Готівка: кур'єр отримає $amount грн\n";
return true;
}
}
function processOrder(PaymentGateway $gateway, float $total): void
{
$gateway->charge($total);
}
$gateways = [new StripeGateway(), new PayPalGateway(), new CashOnDeliveryGateway()];
foreach ($gateways as $gateway) {
processOrder($gateway, 1899.00);
}
Магічні методи
Аналогія:магічні методи — це 'гачки' (hooks), які PHP автоматично викликає в певних ситуаціях. Наприклад, коли backend логує об'єкт Money (сума + валюта) у файл, зручно, щоб echo $money; сам сформував рядок '1899.00 UAH'.
Основні магічні методи:
__construct()/__destruct()— при створенні/знищенні об'єкта.__toString(): string— коли об'єкт намагаються використати як рядок.__get($name)/__set($name, $value)— при зверненні до неіснуючої властивості ззовні.__call($name, $args)/__callStatic()— при виклику неіснуючого методу.__invoke(...$args)— дозволяє викликати об'єкт як функцію.__clone()— при clone $obj.__isset()/__unset()— для isset()/unset() на недоступних властивостях.
Коли НЕ варто використовувати магічні методи: __get/__set роблять код важчим для аналізу IDE і складнішим для дебагу. У сучасному backend-коді їх використовують точково (наприклад в ORM), а не як основний спосіб роботи з властивостями.
<?php
declare(strict_types=1);
final class Money
{
public function __construct(private readonly int $cents, private readonly string $currency = 'UAH') {}
public function __toString(): string
{
return number_format($this->cents / 100, 2) . ' ' . $this->currency;
}
public function __invoke(int $addCents): self
{
return new self($this->cents + $addCents, $this->currency);
}
}
$price = new Money(189900);
echo $price;
$withShipping = $price(5000);
echo $withShipping;
function logOrder(Money $total): void
{
error_log("Сума замовлення: $total");
}
SPL інтерфейси: Countable, ArrayAccess, IteratorAggregate
Аналогія:результат запиту 'знайти всі замовлення клієнта' в реальному backend часто повертають не голий масив, а спеціальний клас-колекцію — так можна одразу дати колекції власні методи (totalAmount(), filterPaid()), при цьому ЗОВНІ вона поводиться так само зручно, як звичайний масив.
Countable (офіційна сигнатура): один метод count(): int.
ArrayAccess (офіційні сигнатури, php.net): чотири методи: offsetExists(mixed $offset): bool, offsetGet(mixed $offset): mixed, offsetSet(mixed $offset, mixed $value): void, offsetUnset(mixed $offset): void.
IteratorAggregate: один метод getIterator(): Traversable, найчастіше повертає ArrayIterator.
Готові класи SPL 'з коробки': ArrayObject вже реалізує всі три інтерфейси одразу, SplStack/SplQueue дають стек/чергу, SplObjectStorage — 'мапу' з об'єктами як ключами.
<?php
declare(strict_types=1);
class OrderCollection implements Countable, ArrayAccess, IteratorAggregate
{
private array $orders = [];
public function count(): int
{
return count($this->orders);
}
public function offsetExists(mixed $offset): bool
{
return isset($this->orders[$offset]);
}
public function offsetGet(mixed $offset): mixed
{
return $this->orders[$offset] ?? null;
}
public function offsetSet(mixed $offset, mixed $value): void
{
if ($offset === null) {
$this->orders[] = $value;
} else {
$this->orders[$offset] = $value;
}
}
public function offsetUnset(mixed $offset): void
{
unset($this->orders[$offset]);
}
public function getIterator(): Iterator
{
return new ArrayIterator($this->orders);
}
}
$orders = new OrderCollection();
$orders[] = 1899.00;
$orders[] = 350.00;
echo count($orders);
foreach ($orders as $order) {
echo $order . "\n";
}
Enum та фінальні класи
Аналогія:статус замовлення в інтернет-магазині має чітко обмежений набір варіантів: 'очікує оплати', 'оплачено', 'відправлено', 'скасовано'. Якщо зберігати статус як звичайний рядок, ніщо не завадить одруківці. Enum (з PHP 8.1) робить такі одруківки неможливими на рівні мови.
Pure enum vs backed enum: 'чистий' enum не має скалярного значення. 'Backed' enum (enum Status: string) прив'язує кожному case конкретне значення, доступне через ->value — потрібно для збереження статусу в базу даних чи JSON.
Обмеження enum: enum МОЖЕ мати методи, константи і реалізовувати інтерфейси, але НЕ може мати звичайні мутабельні властивості.
final клас/метод: final class забороняє успадкування взагалі; final public function забороняє перевизначення конкретного методу.
PHP 8.2 -- readonly class: якщо ВСІ властивості класу readonly, можна позначити readonly одразу весь клас.
<?php
declare(strict_types=1);
enum OrderStatus: string
{
case Pending = 'pending';
case Paid = 'paid';
case Shipped = 'shipped';
case Cancelled = 'cancelled';
public function canBeCancelled(): bool
{
return match($this) {
self::Pending, self::Paid => true,
self::Shipped, self::Cancelled => false,
};
}
public function label(): string
{
return match($this) {
self::Pending => 'Очікує оплати',
self::Paid => 'Оплачено',
self::Shipped => 'Відправлено',
self::Cancelled => 'Скасовано',
};
}
}
final readonly class Invoice
{
public function __construct(
public int $orderId,
public float $amount,
public OrderStatus $status,
) {}
}
$invoice = new Invoice(4521, 1899.00, OrderStatus::Paid);
echo $invoice->status->value;
echo $invoice->status->label();
var_dump($invoice->status->canBeCancelled());