🐘 PHP

Projeto prático final em PHP: um guia completo

Что это такое и зачем? (аналогия из реальной жизни)

Представь, что ты учишься в школе программирования целый год. Сначала ты учил буквы (переменные), потом слова (функции), потом предложения (условные операторы и циклы). И вот настал момент, когда учитель говорит: «А теперь напиши своё собственное сочинение на свободную тему, используя всё, что ты выучил». Это и есть итоговый практический проект.

В реальной жизни это похоже на сборку сложного конструктора LEGO. У тебя есть много разных деталей: колёса (базы данных), моторы (алгоритмы), декоративные элементы (пользовательский интерфейс). Но просто иметь кучу деталей — недостаточно. Нужно собрать из них работающую машину, которая поедет. Итоговый проект — это и есть такая машина.

В мире веб-программирования на PHP итоговый проект — это полноценное веб-приложение. Например, интернет-магазин, блог с комментариями, система управления задачами (Todo-лист) или личный кабинет пользователя. Это не просто отдельные куски кода, а целая система, где всё взаимосвязано: пользователь вводит данные, они сохраняются в базу, обрабатываются и выводятся на экран.

Важно: Итоговый проект — это не экзамен, а твоя визитная карточка. Когда ты будешь устраиваться на работу или показывать портфолио, работодатель в первую очередь посмотрит именно на твои проекты. Поэтому относись к ним как к произведению искусства.

Теория: как это работает изнутри

Давай заглянем «под капот» типичного PHP-проекта. Любое веб-приложение работает по принципу «Запрос — Обработка — Ответ».

  1. Запрос (Request): Пользователь открывает браузер и вводит адрес сайта (например, myshop.ru/catalog). Браузер отправляет HTTP-запрос на сервер.
  2. Обработка (Processing): Сервер (Apache или Nginx) видит, что запрос пришёл к PHP-файлу. Он запускает интерпретатор PHP. PHP-скрипт начинает работать:
    • Подключается к базе данных (MySQL, PostgreSQL).
    • Проверяет, авторизован ли пользователь (смотрит cookies или сессии).
    • Запрашивает из базы список товаров.
    • Формирует HTML-страницу, вставляя данные из базы в нужные места.
  3. Ответ (Response): PHP отдаёт готовую HTML-страницу серверу, а тот отправляет её браузеру пользователя. Пользователь видит красивый каталог товаров.

Внутри самого PHP-кода есть несколько ключевых архитектурных решений, которые ты будешь использовать в итоговом проекте:

  • Маршрутизация (Routing): Это «дорожная карта» твоего сайта. Когда пользователь переходит на /profile, PHP должен понять, что нужно показать профиль, а не каталог. Обычно это делается через единую точку входа (index.php), которая анализирует URL.
  • Шаблонизация (Templating): Чтобы не мешать PHP-код с HTML в одном файле (это очень плохая практика), используют шаблоны. Ты готовишь HTML-каркас, а PHP просто подставляет в него данные.
  • Работа с сессиями: HTTP — это протокол без состояния. Он забывает пользователя сразу после того, как отдал страницу. Чтобы запомнить, что Вася уже залогинился, используются сессии. PHP сохраняет уникальный идентификатор в cookies браузера, а на сервере хранит данные этого пользователя.
  • MVC (Model-View-Controller): Это способ организации кода, который используют 90% профессиональных проектов. Код делится на три слоя:
    • Model (Модель): Отвечает за данные. Работает с базой, выполняет запросы.
    • View (Представление): Отвечает за внешний вид. Содержит HTML и CSS.
    • Controller (Контроллер): Отвечает за логику. Получает запрос, спрашивает у Модели данные, передаёт их во View.

Синтаксис в PHP: все формы и варианты

В итоговом проекте ты будешь использовать не просто базовый синтаксис, а целый набор инструментов. Давай разберём ключевые элементы, которые обязательно встретятся.

1. Подключение файлов

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

// config.php — файл с настройками
<?php
define('DB_HOST', 'localhost'); // Константа — адрес сервера БД
define('DB_NAME', 'my_shop');   // Имя базы данных
define('DB_USER', 'root');      // Имя пользователя
define('DB_PASS', '');          // Пароль
?>

// index.php — главный файл
<?php
require_once 'config.php'; // Подключаем настройки. Если файла нет — ошибка.
// Теперь можно использовать константы DB_HOST, DB_NAME и т.д.
echo 'Подключение к ' . DB_HOST;
?>

2. Работа с базами данных (PDO)

PDO (PHP Data Objects) — это современный и безопасный способ работы с базами данных. Он защищает от SQL-инъекций (когда злоумышленник пытается выполнить свой код через форму ввода).

// Подключение к базе через PDO
$dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8';
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Включаем режим ошибок
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // Результат в виде ассоциативного массива
];

try {
    $pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
} catch (PDOException $e) {
    die('Ошибка подключения: ' . $e->getMessage());
}

// Безопасный запрос с плейсхолдерами
$user_id = 5;
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $user_id]);
$user = $stmt->fetch(); // Получаем одну строку

echo 'Привет, ' . $user['name']; // Выводим имя пользователя

3. Сессии и авторизация

Сессии позволяют «запомнить» пользователя между разными страницами.

// Начало сессии (должно быть в самом начале скрипта, до любого вывода)
session_start();

// Логин пользователя
if ($password_verified) {
    $_SESSION['user_id'] = $user['id']; // Сохраняем ID пользователя в сессию
    $_SESSION['logged_in'] = true;
}

// Проверка авторизации на любой странице
if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
    header('Location: login.php'); // Перенаправляем на страницу входа
    exit; // Останавливаем выполнение скрипта
}

// Выход (logout)
session_destroy(); // Уничтожаем все данные сессии
header('Location: index.php');

4. Обработка форм (GET и POST)

Пользователь вводит данные в форму, и они приходят на сервер.

// HTML-форма (в шаблоне)
// <form method="POST" action="register.php">
//     <input type="text" name="username">
//     <input type="email" name="email">
//     <button type="submit">Зарегистрироваться</button>
// </form>

// register.php — обработчик
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Фильтруем и очищаем входные данные
    $username = htmlspecialchars(trim($_POST['username']));
    $email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
    
    if ($email === false) {
        echo 'Некорректный email';
        exit;
    }
    
    // Теперь можно сохранять данные в базу
    echo "Привет, $username! Твой email: $email";
}

Примеры кода — от простого к сложному

Пример 1: Простой счётчик посещений (файл + сессия)

<?php
session_start(); // Начинаем сессию

// Если в сессии нет счётчика, создаём его
if (!isset($_SESSION['visit_count'])) {
    $_SESSION['visit_count'] = 0; // Начальное значение
}

// Увеличиваем счётчик на 1 при каждом обновлении страницы
$_SESSION['visit_count']++;

// Выводим результат
echo "Вы посетили эту страницу {$_SESSION['visit_count']} раз(а).";
?>

Объяснение: Этот код использует сессии, чтобы запомнить, сколько раз конкретный пользователь обновил страницу. Если закрыть браузер, счётчик сбросится (так как сессия временная).

Пример 2: Гостевая книга (запись в файл)

<?php
$file = 'guestbook.txt'; // Файл для хранения записей

// Обработка формы
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['message'])) {
    $message = htmlspecialchars($_POST['message']); // Защита от XSS
    $entry = date('Y-m-d H:i:s') . " - " . $message . PHP_EOL; // Формат записи
    file_put_contents($file, $entry, FILE_APPEND | LOCK_EX); // Дозапись в файл
}

// Чтение и вывод всех записей
if (file_exists($file)) {
    $entries = file($file, FILE_IGNORE_NEW_LINES); // Читаем файл в массив
    foreach (array_reverse($entries) as $entry) { // Выводим с конца (новые сверху)
        echo "<p>$entry</p>";
    }
}
?>

<!-- Форма для отправки -->
<form method="POST">
    <textarea name="message" required></textarea>
    <button type="submit">Отправить</button>
</form>

Объяснение: Простейшая база данных на текстовом файле. Пользователь пишет сообщение, оно сохраняется в файл с датой. При обновлении страницы все записи читаются и выводятся.

Пример 3: Регистрация и авторизация (база данных)

<?php
// Подключение к БД (предполагаем, что $pdo уже создан)
session_start();

// Регистрация
if (isset($_POST['register'])) {
    $username = trim($_POST['username']);
    $password = password_hash($_POST['password'], PASSWORD_DEFAULT); // Хешируем пароль!
    
    $stmt = $pdo->prepare("INSERT INTO users (username, password) VALUES (:user, :pass)");
    $stmt->execute(['user' => $username, 'pass' => $password]);
    echo "Регистрация успешна!";
}

// Авторизация
if (isset($_POST['login'])) {
    $username = trim($_POST['username']);
    $password = $_POST['password'];
    
    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :user");
    $stmt->execute(['user' => $username]);
    $user = $stmt->fetch();
    
    if ($user && password_verify($password, $user['password'])) {
        $_SESSION['user_id'] = $user['id'];
        echo "Добро пожаловать, $username!";
    } else {
        echo "Неверный логин или пароль";
    }
}
?>

Объяснение: Ключевой момент — password_hash и password_verify. Никогда не храни пароли в открытом виде! Хеш — это «отпечаток» пароля, по которому нельзя восстановить исходный текст.

Пример 4: CRUD — Список задач (Todo List)

<?php
// CRUD: Create, Read, Update, Delete

// Создание задачи
if (isset($_POST['add_task'])) {
    $task = htmlspecialchars($_POST['task']);
    $stmt = $pdo->prepare("INSERT INTO tasks (title, user_id) VALUES (:title, :uid)");
    $stmt->execute(['title' => $task, 'uid' => $_SESSION['user_id']]);
}

// Удаление задачи
if (isset($_GET['delete'])) {
    $id = (int)$_GET['delete']; // Приводим к целому числу для безопасности
    $stmt = $pdo->prepare("DELETE FROM tasks WHERE id = :id AND user_id = :uid");
    $stmt->execute(['id' => $id, 'uid' => $_SESSION['user_id']]);
}

// Чтение всех задач пользователя
$stmt = $pdo->prepare("SELECT * FROM tasks WHERE user_id = :uid ORDER BY created_at DESC");
$stmt->execute(['uid' => $_SESSION['user_id']]);
$tasks = $stmt->fetchAll();

// Вывод задач
foreach ($tasks as $task) {
    echo "<li>{$task['title']} <a href='?delete={$task['id']}'>[Удалить]</a></li>";
}
?>

Объяснение: Это основа любого веб-приложения. Мы создаём запись, читаем список, удаляем по ID. Обрати внимание: при удалении мы проверяем, что задача принадлежит именно этому пользователю (AND user_id = :uid), чтобы один пользователь не мог удалить чужие задачи.

Пример 5: Простая маршрутизация (Front Controller)

<?php
// index.php — единая точка входа
$request = $_SERVER['REQUEST_URI']; // Получаем URL: /catalog, /about и т.д.

// Убираем GET-параметры (всё после ?)
$path = parse_url($request, PHP_URL_PATH);

// Маршрутизация
switch ($path) {
    case '/':
        require 'controllers/home.php';
        break;
    case '/catalog':
        require 'controllers/catalog.php';
        break;
    case '/about':
        require 'controllers/about.php';
        break;
    case '/contact':
        require 'controllers/contact.php';
        break;
    default:
        http_response_code(404); // Отдаём код 404
        require 'views/404.php';
        break;
}
?>

Объяснение: Вместо того чтобы создавать 100 отдельных файлов (catalog.php, about.php), мы создаём один index.php, который смотрит на URL и подключает нужный контроллер. Это удобно для поддержки и безопасности.

Пример 6: Работа с файлами (загрузка аватарки)

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['avatar'])) {
    $file = $_FILES['avatar'];
    $allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
    
    // Проверяем тип файла
    if (!in_array($file['type'], $allowed_types)) {
        die('Можно загружать только JPG, PNG и GIF');
    }
    
    // Проверяем размер (не больше 2 МБ)
    if ($file['size'] > 2 * 1024 * 1024) {
        die('Файл слишком большой');
    }
    
    // Генерируем уникальное имя, чтобы файлы не перезаписывались
    $ext = pathinfo($file['name'], PATHINFO_EXTENSION);
    $new_name = uniqid('avatar_') . '.' . $ext;
    
    // Перемещаем файл из временной папки в постоянную
    move_uploaded_file($file['tmp_name'], 'uploads/' . $new_name);
    
    echo "Аватарка загружена: $new_name";
}
?>

<form method="POST" enctype="multipart/form-data">
    <input type="file" name="avatar" accept="image/*" required>
    <button type="submit">Загрузить</button>
</form>

Объяснение: Загрузка файлов — опасная операция. Мы проверяем тип (чтобы не загрузили PHP-скрипт вместо картинки), размер и даём файлу уникальное имя, чтобы злоумышленник не мог угадать путь к файлу.

Пример 7: REST API — возвращаем JSON

<?php
// api.php — простой REST API
header('Content-Type: application/json'); // Говорим, что ответ будет в JSON

$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path_parts = explode('/', trim($path, '/'));

// Пример: /api/users/5
if ($path_parts[0] === 'api' && $path_parts[1] === 'users') {
    $user_id = $path_parts[2] ?? null;
    
    switch ($method) {
        case 'GET':
            if ($user_id) {
                // Получить одного пользователя
                $stmt = $pdo->prepare("SELECT id, username, email FROM users WHERE id = :id");
                $stmt->execute(['id' => $user_id]);
                $user = $stmt->fetch();
                echo json_encode($user ?: ['error' => 'Пользователь не найден']);
            } else {
                // Получить всех пользователей
                $stmt = $pdo->query("SELECT id, username, email FROM users");
                echo json_encode($stmt->fetchAll());
            }
            break;
        case 'POST':
            // Создать нового пользователя
            $data = json_decode(file_get_contents('php://input'), true);
            // ... логика создания
            echo json_encode(['status' => 'created']);
            break;
    }
}
?>

Объяснение: Современные сайты часто работают как API (интерфейс программирования приложений). Вместо HTML они возвращают JSON — данные, которые может прочитать другая программа (например, мобильное приложение или JavaScript на фронтенде).

Сравнение с Python: чем похоже и чем отличается

Характеристика PHP Python (Django/Flask)
Основное назначение Создан специально для веба. Код встраивается прямо в HTML. Универсальный язык. Для веба используются фреймворки.
Синтаксис Переменные начинаются с $. Концы строк обязательны (;). Чистый синтаксис. Отступы определяют блоки кода.
Шаблоны Можно мешать PHP и HTML в одном файле (но не стоит). Есть Twig. Строгое разделение. Django использует свой шаблонизатор.
Базы данных PDO — лёгкий и быстрый. Нет встроенной ORM. Django ORM — мощная система, превращает таблицы в объекты.
Производительность Очень быстрый для простых сайтов. Много встроенных функций. Медленнее на простых задачах, но быстрее на сложных вычислениях.
Хостинг Поддерживается практически любым хостингом. Дешёво. Требует более продвинутой настройки сервера.
Сообщество Огромное количество готовых CMS (WordPress, Joomla). Много фреймворков для науки о данных и веба.
Совет: Если твоя цель — быстро сделать сайт с нуля и разместить его на дешёвом хостинге — выбирай PHP. Если ты хочешь писать сложную логику, работать с нейросетями или делать большие корпоративные системы — Python будет удобнее.

Типичные ошибки и подводные камни

  1. SQL-инъекции: Никогда не подставляй переменные прямо в SQL-запрос.
    // ПЛОХО (опасно!)
    $sql = "SELECT * FROM users WHERE id = " . $_GET['id'];
    
    // ХОРОШО (безопасно)
    $stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
    $stmt->execute(['id' => $_GET['id']]);
  2. XSS (межсайтовый скриптинг): Всегда экранируй вывод.
    // ПЛОХО (если $name = "<script>alert('xss')</script>")
    echo "Привет, $name";
    
    // ХОРОШО
    echo "Привет, " . htmlspecialchars($name);
  3. Забытая директива exit после header():
    // ПЛОХО — код продолжит выполняться!
    header('Location: login.php');
    // ... здесь может быть опасный код
    
    // ХОРОШО
    header('Location: login.php');
    exit;
  4. Сравнение с == вместо ===:
    // ПЛОХО — строка "0" равна false!
    if ($_POST['admin'] == false) { ... }
    
    // ХОРОШО — строгое сравнение по типу
    if ($_POST['admin'] === false) { ... }
  5. Игнорирование ошибок: Никогда не используй @ для подавления ошибок. Всегда включай отображение ошибок на этапе разработки.
  6. Хранение паролей в открытом виде: Используй только password_hash().
  7. Слишком большие файлы загрузки: Всегда проверяй размер и тип файла на сервере, не полагайся только на клиентскую проверку.

Лучшие практики и советы профессионалов

  1. Используй PSR-4 и автозагрузку Composer. Не пиши require_once для каждого файла вручную. Composer сам найдёт и подключит нужный класс.
  2. Разделяй код на слои (MVC). Даже если ты не используешь фреймворк, создай папки controllers/, models/, views/. Это спасёт тебя, когда проект вырастет.
  3. Не доверяй пользовательскому вводу. Каждый $_GET, $_POST, $_COOKIE и $_FILES — потенциальная атака. Фильтруй, валидируй, экранируй.
  4. Используй Git. Даже для учебного проекта. Команда git init в начале проекта спасёт тебя от потери кода.
  5. Пиши комментарии на русском для сложных мест. Через месяц ты сам забудешь, что делает этот кусок кода. Комментарий «// Проверяем, есть ли у пользователя права администратора» сэкономит тебе часы времени.
  6. Используй .env файлы для секретов. Никогда не храни пароли от базы данных в коде. Используй библиотеку vlucas/phpdotenv.
  7. Делай резервные копии базы данных. Одна случайная команда DROP TABLE — и весь проект насмарку. Автоматизируй бекапы.
  8. Тестируй на разных браузерах и устройствах. То, что отлично работает в Chrome на ноутбуке, может развалиться в Safari на iPhone.

Вопросы для самопроверки

  1. Вопрос: Что такое SQL-инъекция и как от неё защититься в PHP?
    Ответ: Это атака, когда злоумышленник вводит SQL-код в форму. Защита — использовать подготовленные запросы (PDO) с плейсхолдерами (:id), а не подставлять переменные напрямую в строку запроса.
  2. Вопрос: Чем отличается require от include?
    Ответ: require вызывает фатальную ошибку и останавливает скрипт, если файл не найден. include выдаёт только предупреждение и продолжает работу.
  3. Вопрос: Зачем нужна функция htmlspecialchars()?
    Ответ: Она преобразует специальные HTML-символы (<, >, &) в их безопасные эквиваленты. Это защищает от XSS-атак, когда злоумышленник пытается внедрить JavaScript-код через текстовые поля.
  4. Вопрос: Что такое сессия и как она работает?
    Ответ: Сессия — это способ хранить данные пользователя между запросами. PHP создаёт уникальный ID, сохраняет его в cookies браузера, а на сервере в файле или базе данных хранит массив $_SESSION с данными этого пользователя.
  5. Вопрос: Почему нельзя хранить пароли в открытом виде?
    Ответ: Если база данных будет скомпрометирована (украдена), злоумышленник получит все пароли пользователей. Хеширование (через password_hash()) превращает пароль в строку, из которой невозможно восстановить исходный пароль.
  6. Вопрос: Что такое MVC и зачем он нужен?
    Ответ: Model-View-Controller — это архитектурный паттерн, который разделяет код на три части: данные (Model), отображение (View) и логику (Controller). Это делает код понятным, тестируемым и лёгким для поддержки.
  7. Вопрос: Как правильно обработать загрузку файла на сервер?
    Ответ: Проверить тип файла (MIME), размер, сгенерировать уникальное имя, переместить из временной папки в постоянную с помощью move_uploaded_file(). Никогда не используй оригинальное имя файла от пользователя.
  8. Вопрос: Что такое REST API и чем JSON отличается от HTML?
    Ответ: REST API — это способ общения программ через HTTP. Вместо HTML (который читает человек) API возвращает JSON (который читает программа). JSON — это текстовый формат данных, похожий на массивы PHP.
  9. Вопрос: Как защитить страницу от неавторизованного доступа?
    Ответ: В начале каждой защищённой страницы проверять $_SESSION['logged_in']. Если пользователь не авторизован — перенаправлять его на страницу входа через header('Location: login.php'); exit;.
  10. Вопрос: Что такое автозагрузка Composer и зачем она нужна?
    Ответ: Composer — это менеджер пакетов для PHP. Он автоматически подключает нужные файлы (классы) по требованию, избавляя от необходимости писать десятки require_once вручную. Достаточно один раз выполнить composer require для установки библиотеки.

Итог и что изучить дальше

Итоговый практический проект — это твой шанс собрать всё, что ты учил, в единую работающую систему. Ты научился:

  • Структурировать код с помощью MVC.
  • Безопасно работать с базами данных через PDO.
  • Управлять пользователями (регистрация, авторизация, сессии).
  • Обрабатывать формы и загружать файлы.
  • Создавать простые API и мар