PHP — язык, на котором работает треть веба

PHP — самый простой способ сделать веб-страницу динамической: код выполняется на сервере и вставляет данные в HTML. На PHP работают WordPress, Bitrix и миллионы сайтов, а деплой на хостинг занимает минуты. Отличный выбор для первого серверного языка.

Уровень: начальный 19 разделов 3–4 часа

1. Что такое PHP

PHP (расшифровка — PHP: Hypertext Preprocessor) — скриптовый язык, который выполняется на сервере. Когда посетитель открывает страницу, сервер обрабатывает PHP-код и отдаёт результат — обычно HTML. Посетитель никогда не видит сам код.

ПлюсыМинусы
легко начатьдинамическая типизация (ошибки позже)
дешёвый хостинг для PHPпрошлый код бывает неаккуратным
огромная база готовых решенийдля больших проектов нужны дисциплина и фреймворки
отличная интеграция с MySQLне для высоконагруженных реального времени
PHP не требует сборки: сохранил файл и обновил страницу — изменения уже видны. Это делает его идеальным для обучения и быстрых сайтов.

2. Установка XAMPP и первая программа

Для локальной разработки проще всего установить XAMPP — готовый набор из PHP, веб-сервера Apache и базы MySQL. Один установщик — и всё работает.

1. Скачай XAMPP с официального сайта apachefriends.org
2. Запусти установщик и установи в C:\xampp
3. Открой панель XAMPP Control Panel
4. Нажми Start напротив Apache и MySQL
5. В браузере открой http://localhost

PHP-файлы хранятся в папке htdocs. Создай там файл index.php:

<?php
/* PHP-код начинается тегом  */
echo "Привет, мир!";
?>

Открой http://localhost/index.php — увидишь «Привет, мир!». PHP тесно переплетается с HTML:

<!DOCTYPE html>
<html lang="ru">
<body>
  <h1>Сейчас <?php echo date("Y") ?> год</h1>
  <p><?php echo "Код выполнился на сервере"; ?></p>
</body>
</html>
Просмотри «Исходный код страницы» в браузере — PHP-кода там нет, только его результат. Всё, что между <?php и ?>, обрабатывается на сервере и в HTML не попадает.
Короткий тег <? ... ?> может быть отключён. Всегда используй полный <?php — так скрипт заработает на любом хостинге.

3. Переменные, типы и вывод

Переменные в PHP начинаются со знака $. Язык динамически типизирован: тип определяется значением.

<?php
$name = "Аня";         // строка
$age = 25;             // целое число
$height = 1.72;        // число с плавающей точкой
$active = true;        // булево значение
$balance = null;       // пустое значение

echo "Привет, $name!";          // двойные кавычки подставляют переменные
echo 'Привет, ' . $name . '!';  // одинарные — только конкатенация (.)

echo gettype($age);     // integer — тип значения
?>

Основные типы

ТипОписаниеПример
stringстрока текста"привет"
intцелое число42
floatдробное число3.14
boolистина/ложьtrue, false
arrayмассив значений[1, 2, 3]
nullотсутствие значенияnull
// приведение типов
$num = "25";
$num = (int) $num;         // 25 честным числом

// проверка существования и пустоты
if (isset($name) && !empty($name)) {
    echo $name;
}

// тернарный оператор
$status = $active ? "активен" : "неактивен";
Для отладки используй var_dump($var) — он показывает тип и значение, а print_r($arr) красиво выводит массивы.
Двойные кавычки отличаются от одинарных: в них переменные подставляются, а управляющие последовательности (например, \n) работают. Для простых строк и операций с базой привычнее одинарные — они чуть быстрее и предсказуемее.

4. Массивы

Массив хранит набор значений по ключу. PHP поддерживает и индексные, и ассоциативные массивы.

<?php
$colors = ["красный", "зелёный", "синий"];
echo $colors[0];              // красный

$user = [
    "name" => "Аня",
    "age"  => 25,
    "city" => "Москва",
];
echo $user["name"];           // Аня

// добавление
$colors[] = "жёлтый";         // в конец индексного массива
$user["job"] = "разработчик"; // новая пара «ключ => значение»

// перебор
foreach ($colors as $color) {
    echo $color . " ";
}

foreach ($user as $key => $value) {
    echo "$key: $value; ";
}
// полезные функции
count($colors);               // количество элементов
sort($colors);                // сортировка по значениям
array_merge($a, $b);          // объединение массивов
in_array("синий", $colors);   // проверка вхождения
array_key_exists("name", $user); // есть ли ключ
implode(", ", $colors);       // массив в строку
explode(",", "a,b,c");        // строка в массив
// многомерный массив
$cart = [
    ["product" => "Кроссовки", "price" => 4990],
    ["product" => "Шапка", "price" => 990],
];

$total = 0;
foreach ($cart as $item) {
    $total += $item["price"];
}
echo "Итого: $total";
print_r и var_dump — твои лучшие друзья при работе с массивами. Непонятная структура данных выводится на экран одной строкой.

5. Условия и циклы

Управляющие конструкции в PHP привычны любому, кто видел JavaScript: if-else, switch, for, while и foreach.

<?php
$score = 85;

if ($score >= 90) {
    echo "Отлично";
} elseif ($score >= 60) {
    echo "Хорошо";
} else {
    echo "Подтянись";
}

$day = "saturday";
switch ($day) {
    case "saturday":
    case "sunday":
        echo "Выходной";
        break;
    default:
        echo "Рабочий день";
}
// циклы
for ($i = 1; $i <= 5; $i++) {
    echo $i . " ";
}

$n = 10;
while ($n > 0) {
    $n--;
}

$colors = ["красный", "зелёный", "синий"];
foreach ($colors as $color) {
    echo $color . " ";
}

// break и continue
foreach (range(1, 10) as $i) {
    if ($i % 2 === 0) continue;   // пропустить чётные
    if ($i > 7) break;            // остановиться
    echo $i . " ";                // 1 3 5 7
}
В PHP удобная вставка циклов прямо в HTML: открываешь тег и работаешь с каждым элементом, закрываешь . Читается как обычная разметка.
<ul>
  <?php foreach ($products as $p): ?>
    <li><?= $p["name"] ?> — <?= $p["price"] ?> руб.</li>
  <?php endforeach; ?>
</ul>
Короткая форма вывода <?= $var ?> эквивалентна <?php echo $var ?> и почти всегда включена. Это самый быстрый способ вставить переменную в HTML.

6. Функции

Функции группируют код для переиспользования и делают программу структурированной.

<?php
function add($a, $b) {
    return $a + $b;
}

// параметр по умолчанию
function greet($name, $prefix = "Привет") {
    return "$prefix, $name!";
}

echo add(2, 3);            // 5
echo greet("Аня");         // Привет, Аня!
echo greet("Боря", "Здравствуйте"); // Здравствуйте, Боря!
// типизированные параметры и результат
function multiply(int $a, int $b): int {
    return $a * $b;
}

// функции принимают массив «по ссылке» только с &
function appendItem(array &$list, $item) {
    $list[] = $item;
}

$items = [1, 2];
appendItem($items, 3);   // $items теперь [1, 2, 3]

// стрелочные и анонимные функции
$double = fn($x) => $x * 2;
$nums = array_map($double, [1, 2, 3]);  // [2, 4, 6]
ЭлементОписание
function f($a)объявление функции
return $value;возврат результата
function f($a = 1)значение параметра по умолчанию
f(...$args)переменное число аргументов
fn($x) => exprстрелочная функция
Глобальные переменные внутри функции «не видны»: нужно объявить их через global или передать аргументом. Передача аргументами — почти всегда лучший вариант: так код проще тестировать.

7. Формы: $_POST и $_GET

Данные HTML-форм попадают в суперглобальные массивы: $_GET (параметры из URL) и $_POST (тело запроса). Это ядро любого сайта с обратной связью.

<!-- form.html -->
<form action="register.php" method="post">
  <label>Имя: <input type="text" name="name"></label><br>
  <label>Email: <input type="email" name="email"></label><br>
  <button type="submit">Отправить</button>
</form>
<?php
// register.php — обработчик формы
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $name  = trim($_POST["name"] ?? "");
    $email = trim($_POST["email"] ?? "");

    $errors = [];

    if ($name === "") {
        $errors[] = "Имя обязательно";
    }
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = "Некорректный email";
    }

    if ($errors) {
        foreach ($errors as $e) {
            echo "<p>$e</p>";
        }
    } else {
        echo "<p>Спасибо, $name! Письмо отправлено на $email.</p>";
    }
}
// GET-параметры: /search.php?q=носки
$query = trim($_GET["q"] ?? "");
echo "Поиск: " . htmlspecialchars($query);

Разница GET и POST

GETPOST
данные в URLданные в теле запроса
видно в адресной строкескрыто от глаз
для поиска, фильтров, ссылокдля форм, заказов, изменения данных
можно закладой сохранитьне сохраняется как ссылка
Никогда не вставляй данные пользователя в HTML без экранирования: вывод через htmlspecialchars($value) защищает от XSS-атак. Для SQL используй PDO prepared statements — об этом в разделе 8.
Оператор ?? («null coalescing») заменяет проверку isset: $_POST["name"] ?? "" вернёт пустую строку, если ключа нет. Пиши так всегда — без предупреждений об отсутствии ключа.

8. Работа с базой данных: PDO

PDO (PHP Data Objects) — единый интерфейс для работы с разными базами: MySQL, PostgreSQL, SQLite. Обязательное правило — prepared statements, защищающие от SQL-инъекций.

<?php
$dsn = "mysql:host=localhost;dbname=shop;charset=utf8mb4";
$user = "root";
$pass = "";

try {
    $pdo = new PDO($dsn, $user, $pass, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
} catch (PDOException $e) {
    die("Ошибка подключения к базе.");  // логируй $e->getMessage()
}

// добавление пользователя — плейсхолдеры вместо склейки строк
$sql = "INSERT INTO users (name, email) VALUES (:name, :email)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
    ":name"  => $name,
    ":email" => $email,
]);
// чтение данных
$rows = $pdo->query("SELECT * FROM products")->fetchAll();

// замена с параметрами
$sql = "SELECT * FROM products WHERE price > ? AND in_stock = 1";
$stmt = $pdo->prepare($sql);
$stmt->execute([1000]);
$rows = $stmt->fetchAll();

foreach ($rows as $row) {
    echo $row["name"] . " — " . $row["price"] . " руб.<br>";
}
// подготовленный запрос с условием из формы
$id = $_GET["id"] ?? 0;
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$id]);
$product = $stmt->fetch();
Никогда не строй SQL склейкой строк с данными пользователя: $sql = "SELECT * FROM users WHERE email = '$email'" — классическая SQL-инъекция. Только плейсхолдеры (?) или именованные (:name).
Хешируй пароли функцией password_hash($pass, PASSWORD_DEFAULT) и проверяй через password_verify(). Никогда не храни пароли в открытом виде и не придумывай собственную «криптографию».
SQL понадобится и в других курсах — основы синтаксиса смотри в отдельном уроке SQL, а особенности MySQL — в MySQL.

9. Сессии и куки

HTTP не помнит предыдущие запросы. Куки — маленькие данные на стороне браузера, а сессии — данные на сервере, связанные с куки-идентификатором.

<?php
// старт сессии — обязательно до любого вывода
session_start();

// сохраняем данные
$_SESSION["user_id"] = 42;
$_SESSION["cart"] = [101, 102, 103];

// читаем в другом запросе
if (isset($_SESSION["user_id"])) {
    echo "Вы вошли, id = " . $_SESSION["user_id"];
}

// выход — очистка сессии
session_unset();
session_destroy();
// куки: setcookie вызывается до вывода HTML
$expires = time() + 60 * 60 * 24 * 30;   // +30 дней
setcookie("theme", "dark", $expires, "/", "", false, true);

// чтение куки
$theme = $_COOKIE["theme"] ?? "light";

// удаление — срок в прошлом
setcookie("theme", "", time() - 3600, "/");

Куки против сессий

КукиСессии
хранятся у клиента (браузере)хранятся на сервере
видно и можно подделатьклиент видит только id сессии
подходит для настроекподходит для корзины, входа
размер ~4 КБразмер ограничен сервером
Вызов session_start() и setcookie() обязан идти до любого вывода (echo, HTML). Иначе — предупреждение «headers already sent», и сессия не сохранится.
Устанавливай флаг HttpOnly для куки (true последним параметром) — тогда доступ к куки из JavaScript будет закрыт, а значит, меньше риск кражи через XSS.

10. Основы ООП

Класс — это шаблон объекта: свойства хранят данные, методы — поведение. Классы помогают объединять логику и переиспользовать код.

<?php
class Product
{
    public string $name;
    public float $price;
    private int $stock = 0;

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

    public function totalCost(int $qty): float
    {
        return $this->price * $qty;
    }

    public function isInStock(): bool
    {
        return $this->stock > 0;
    }
}

$shoes = new Product("Кроссовки", 4990);
echo $shoes->name;              // Кроссовки
echo $shoes->totalCost(2);      // 9980
// наследование и переопределение
class DiscountProduct extends Product
{
    private float $discount;   // 0..1

    public function __construct(string $name, float $price, float $discount)
    {
        parent::__construct($name, $price);
        $this->discount = min(1, max(0, $discount));
    }

    public function totalCost(int $qty): float
    {
        return parent::totalCost($qty) * (1 - $this->discount);
    }
}

$sale = new DiscountProduct("Шапка", 990, 0.2);
echo $sale->totalCost(1);       // 792

Модификаторы доступа

МодификаторВидимость
publicдоступно отовсюду
protectedкласс и потомки
privateтолько внутри класса
Для больших проектов не пиши весь код в одном файле: используй автозагрузку классов (Composer + PSR-4) или готовые фреймворки вроде Laravel и Symfony.

11. Деплой на хостинг

Деплой PHP-сайта прост: скопировал файлы на сервер — и сайт работает. Обычный виртуальный хостинг с панелью (cPanel, ISPmanager) справится с задачей за пятнадцать минут.

1. Купи хостинг с поддержкой PHP и MySQL
2. Создай базу данных и пользователя в панели
3. Загрузи файлы проекта в папку public_html (FTP или админка)
4. Настрой подключение к базе через переменные окружения
5. Переведи PHP на актуальную версию в панели
6. Подключи домен и бесплатный SSL-сертификат
<?php
// config.php — подключение к базе через переменные среды
$dbHost = getenv("DB_HOST") ?: "localhost";
$dbName = getenv("DB_NAME") ?: "my_shop";
$dbUser = getenv("DB_USER") ?: "root";
$dbPass = getenv("DB_PASS") ?: "";

// на локальном XAMPP переменных нет — берутся значения по умолчанию

Чек-лист перед публикацией

Не клади файлы конфигурации с паролями в public_html под общим именем (config.php с секретами). Храни секреты в переменных окружения или за пределами корня сайта, где они недоступны по URL.
Если планируешь выносить логику на отдельный сервер или строить микросервисы — посмотри Хостинг и деплой и Микросервисы.

12. Загрузка файлов на сервер

Работа с файлами в PHP начинается с суперглобального массива $_FILES. Никогда не доверяйте имени файла от клиента: проверяйте тип, размер и перемещайте его в изолированную директорию.

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $file = $_FILES['avatar'] ?? null;
  if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
    exit('Ошибка загрузки');
  }
  $allowed = ['image/jpeg', 'image/png'];
  if (!in_array(mime_content_type($file['tmp_name']), $allowed)) {
    exit('Недопустимый тип файла');
  }
  $ext = pathinfo($file['name'], PATHINFO_EXTENSION);
  $name = bin2hex(random_bytes(8)) . '.' . $ext;
  move_uploaded_file($file['tmp_name'],
                     __DIR__ . '/uploads/' . $name);
}
Всегда ограничивай размер файла настройкой upload_max_filesize и проверяй $_FILES['avatar']['size'] на сервере — клиент легко подделывает поле размера в запросе.
Не сохраняй файл с оригинальным именем: атакующий передаст shell.php, и сервер его выполнит. Генерируй случайное имя и держи папку загрузок без права выполнения PHP (php_flag engine off) либо за пределами webroot.

13. Composer и автозагрузка PSR-4

Composer — менеджер зависимостей PHP. Он качает библиотеки, обновляет их и генерирует автозагрузку: все классы подключаются автоматически по структуре PSR-4, без ручных require.

// composer.json
{
    "require": {
        "monolog/monolog": "^3.0"
    },
    "autoload": {
        "psr-4": { "App\\": "src/" }
    }
}

composer install
require __DIR__ . '/vendor/autoload.php';

PSR-4 означает: пространство имён App\Cart соответствует пути src/Cart.php. Класс App\Models\User лежит в src/Models/User.php.

Обновляй зависимости через composer update, а composer.lock храни в репозитории: он фиксирует точные версии и делает деплой воспроизводимым.
Минимизируй зависимости: каждая — это обновления, известные уязвимости и совместимость. Минимализм ускоряет сборку и упрощает поддержку кода.

14. Сессии, CSRF и безопасность паролей

Форма без CSRF-защиты принимает подделанные запросы с других сайтов. Стандартный приём — токен в сессии, который проверяется при каждом POST.

<?php
session_start();

if (empty($_SESSION['csrf'])) {
    $_SESSION['csrf'] = bin2hex(random_bytes(32));
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
        exit('CSRF-проверка не пройдена');
    }
}

$hash = password_hash($_POST['password'], PASSWORD_DEFAULT);
$ok   = password_verify($_POST['password'], $hash);
POST не защищает от CSRF сам по себе: поддельная страница отправляет форму с этого же браузера. Проверка токена — единственный полный барьер. Никогда не используй самописные алгоритмы хэширования.

15. Тестирование с PHPUnit

Автотесты ловят регрессии при любых изменениях. PHPUnit — стандарт для PHP: класс с методами test*, набор assertions и запуск из CLI.

use PHPUnit\Framework\TestCase;

final class CalculatorTest extends TestCase
{
    public function testAdd(): void
    {
        $calc = new Calculator();
        $this->assertSame(4, $calc->add(2, 2));
    }
}

vendor/bin/phpunit tests
AssertЧто проверяет
assertSameзначение и тип
assertContainsэлемент в массиве
expectExceptionожидаемое исключение
assertCountчисло элементов
Тесты с маленькими изолированными функциями писать проще. Запускай в CI: composer test выполняется на каждый pull-request — ошибка не попадёт в прод.
Покрывай тестами критичные алгоритмы: скидки, налоги, проверки доступа. Стопроцентное покрытие не самоцель — важнее ценность для регрессий.

16. Современный PHP 8: match, readonly, enums

PHP 8 принёс выразительные конструкции, делающие код короче и безопаснее. match — «ранний выход» из длинных if/elseif: он возвращает значение строго и бросает UnhandledMatchError при отсутствии подходящего варианта. readonly свойства и enum — типизированные доменные значения.

enum OrderStatus: string {
    case New = 'new';
    case Paid = 'paid';
    case Done = 'done';
}

function label(OrderStatus $status): string {
    return match ($status) {
        OrderStatus::New      => 'Новый',
        OrderStatus::Paid     => 'Оплачен',
        OrderStatus::Done     => 'Выполнен',
    };
}

$user = new class { public readonly string $id; };

В PHP 8.2 появились только-read классы (objective: readonly), а в 8.3 — типизированные class-константы. Практическая выгода: меньше ветвлений, меньше «магических» строк, IDE предупреждает о пропущенных кейсах enum — пересчёт логики смены статусов становится явным.

Используйте enums для типов транзакций, ролей и статусов: вместо строк в БД — константа, вместо хрупких сравнений — типизированный аргумент.
match сравнивает строго (===) и требует полного покрытия всех веток значения — это нормально для enum, но для неограниченных строк добавьте default-ветку.

17. Работа с REST API и вебхуками

PHP-приложение редко живёт в вакууме: оплата, рассылки, карты — всё это внешние сервисы, с которыми общаются через HTTP. Современный подход — пакет Guzzle или встроенный клиент, но для быстрого прототипа хватает curl. Парсинг ответа JSON — функция json_decode с флагом ассоциативного массива.

$data = [
    'amount' => 100,
    'currency' => 'USD',
];

$ch = curl_init('https://api.example.com/pay');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS => json_encode($data),
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
$resp = json_decode(curl_exec($ch), true);
curl_close($ch);

Вебхуки — обратное направление: внешний сервис сам присылает события (платёж проведён, письмо открыто). Обязательно валидируйте входящий вебхук: проверяйте подпись (например, HMAC по секрету) и повторяющиеся ID, чтобы один запрос не выполнился дважды. Обработку делайте «идемпотентной».

Никогда не передавайте секреты API в коде страниц — только в конфигурации на сервере, и не логируйте полные токены.

18. Очереди и фоновые задачи

Тяжёлые операции (рассылка, обработка файлов, оплата интеграций) не должны блокировать запрос пользователя. Фоновые задачи в PHP запускаются из CLI: скрипт вызывается cron-ом, а между процессами данные передаются через очередь (Redis, RabbitMQ, таблицу в БД). Пользователь получает ответ мгновенно, работа идёт в фоне.

// в обработчике формы — положить задачу в очередь
$redis->lpush('emails', json_encode(['to' => $email,
                                     'subject' => $subject]));

// в фоновом воркере (cron каждую минуту)
while ($job = $redis->rpop('emails')) {
    $item = json_decode($job, true);
    send_mail($item['to'], $item['subject']);
}

Для надёжности очередь должна переживать падение воркера: используйте структуру, где после извлечения задача подтверждается явно (ACK) — например, через список + отдельная «in-progress» очередь, обрабатываемая по таймауту. Объём и повторы считайте как метрики, чтобы вы не теряли задачи молча.

Начните с Redis: один сервер закрывает 90% задач по очередям и кэшу. RabbitMQ появляется, когда нужны приоритеты, отложенные задачи и несколько потребителей с балансировкой.
CRON без семантики повторных попыток превращает сбой внешнего сервиса в вечные ошибки лога: добавьте максимальное число retry и backoff.

19. Производительность: кэширование и opcache

PHP исполняется построчно каждый запрос, поэтому главный ускоритель — opcache: он хранит скомпилированные байт-коды скриптов в памяти, а не перечитывает файлы с диска при каждом заходе. Включите его и следите за метрикой opcache_get_status() — reuse ratio должен быть высоким.

; php.ini
opcache.enable = 1
opcache.enable_cli = 0
opcache.memory_consumption = 128
opcache.validate_timestamps = 1
opcache.file_update_protection = 2

Второй уровень — кэш данных в Redis (или APCu): тяжёлые запросы к БД, HTML-фрагменты и сессии складываются в память и отдаются мгновенно. Ключи подписывайте версиями (users:v2), чтобы инвалидация была предсказуемой, а TTL задавайте под частоту обновления данных.

Сжатие ответа gzip/brotli на уровне Nginx экономит трафик в 3–5 раз, что часто «задевает» и время отклика, и стоимость хостинга.
Не кэшируйте то, что меняется чаще минуты — например, счётчик онлайн-стов: это равенство «кэш = ложь», только время расхождения выберете вы.

Что дальше

Ты освоил PHP от установки XAMPP до деплоя: переменные, массивы, условия, функции, формы, базы через PDO, сессии и основы ООП. Дальше рекомендуем углубиться в SQL и MySQL, освежить разметку в HTML и попробовать фреймворк Laravel. Основы сайтов в целом — в курсе Хостинг и деплой.

← Вернуться к курсам