Практика Junior: безопасность, Composer, тестирование, мини-проект
Что вы уже умеете и что прокачаем в этой теме
В предыдущих уроках вы прошли цепочку, которая встречается в реальных PHP-проектах:
основы синтаксиса, типов и функций
веб-контекст: HTTP, формы, сессии, cookies
хранение данных: файлы, JSON, MySQL + PDO
ООП: классы, интерфейсы, пространства имён и автозагрузкаВ этой статье вы соберёте навыки уровня Junior, которые отличают «код, который работает» от «кода, который можно поддерживать»:
базовая безопасность веб-приложений по умолчанию
Composer как стандарт управления зависимостями и автозагрузкой
тестирование логики (минимум, который реально применяют)
мини-проект: структура, слои, точки расширения!Схема слоёв мини‑проекта и направлений зависимостей
Безопасность: минимум, который должен быть в каждом проекте
Безопасность в PHP — это не «набор трюков», а привычка: любые внешние данные считаем недоверенными, а выход на страницу — потенциально опасным.
Полезные источники:
OWASP Top 10
PHP Manual: SecurityВалидация входа и нормализация
Данные из _POST почти всегда приходят строками, поэтому вы делаете два шага:
нормализация: trim, приведение типов, приведение регистра, удаление лишних пробелов
валидация: проверки формата и допустимых значенийПример: аккуратно достать число страницы из pageRaw = page = (int) page < 1) {
value): string
{
return htmlspecialchars(name = name);
php
<?php
declare(strict_types=1);
_POST['email'] ?? '';
pdo->prepare('SELECT id, email FROM users WHERE email = :email');
email]);
stmt->fetch();
php
<?php
declare(strict_types=1);
password, PASSWORD_DEFAULT);
if (!password_verify(hash)) {
// неверный пароль
}
php
<?php
declare(strict_types=1);
session_start();
if (_SESSION['user_id'] = _SESSION['_csrf'])) {
_SESSION['_csrf'];
}
function csrfCheck(?string _SESSION['_csrf'])) {
return false;
}
if (!is_string(token === '') {
return false;
}
return hash_equals(token);
}
bash
composer --version
bash
composer init
json
{
"name": "app/mini-project",
"require": {
"php": ">=8.1"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
bash
composer dump-autoload
php
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
bash
composer require vendor/package
bash
composer require --dev vendor/package
bash
composer require --dev phpunit/phpunit
php
<?php
declare(strict_types=1);
namespace App\Contracts;
interface UserRepositoryInterface
{
public function findByEmail(string email, string message): void;
}
php
<?php
declare(strict_types=1);
namespace App\Services;
use App\Contracts\LoggerInterface;
use App\Contracts\UserRepositoryInterface;
use InvalidArgumentException;
use RuntimeException;
final class UserService
{
public function __construct(
private UserRepositoryInterface logger,
) {
}
public function register(string password): int
{
email);
if (filter_var(password) === '') {
throw new InvalidArgumentException('Пароль пустой');
}
if (email) !== null) {
throw new RuntimeException('Пользователь уже существует');
}
password, PASSWORD_DEFAULT);
this->users->create(hash);
id);
return usersByEmail = [];
private int email): ?array
{
return email] ?? null;
}
public function create(string passwordHash): int
{
this->id++;
email] = ['id' => email, 'hash' => id;
}
}
final class NullLogger implements LoggerInterface
{
public function info(string service = new UserService(new InMemoryUsers(), new NullLogger());
service->register('not-an-email', 'secret');
}
public function testRegisterCreatesUser(): void
{
id = this->assertSame(1, _SERVER['REQUEST_URI'].
Пример Kernel, который направляет запрос:
Это не «идеально», но это хороший учебный шаг: вы видите границы между HTTP и логикой.
Сервисный слой: правила вместо скриптов
Рекомендуемая практика:
всё, что можно назвать «правилами», выносить в сервисы
сервисы получают зависимости (репозитории, логгеры) через конструкторПримеры сервисов мини-проекта:
AuthService: регистрация, вход, работа с сессией
NotesService: создать заметку, удалить заметку, получить списокРепозитории: только доступ к данным
Репозитории инкапсулируют PDO и SQL:
UserRepositoryPdo
NoteRepositoryPdoТакой код проще:
тестировать (можно подменить интерфейс)
переписать (например, поменять структуру таблиц)Шаблоны: безопасный вывод и CSRF
Пример фрагмента формы (только идея):
выводить значения через e(...)
добавлять скрытое поле _csrfCSRF-токен вы берёте из функции csrfToken() и вставляете в форму.
Чеклист «готовности» мини-проекта
везде, где выводится пользовательский текст, используется htmlspecialchars
все SQL-запросы с пользовательскими параметрами сделаны через prepare
все формы, которые меняют данные, защищены CSRF-токеном
после успешного POST используется паттерн POST-Redirect-GET
Composer подключён, классы грузятся через PSR-4
есть хотя бы 2 теста на сервисный слойКуда двигаться дальше после этой практики
Когда мини-проект собран и работает, вы уже близки к Junior-уровню. Следующие логичные шаги:
добавить централизованную обработку ошибок и логирование
добавить конфигурацию окружения (например, параметры БД) через отдельный конфиг
подключить статический анализатор и форматтер (когда почувствуете потребность)
перейти на фреймворк (Laravel или Symfony), чтобы увидеть те же принципы на «боевом» каркасе