May 31

PHP для профессионалов: продвинутые приемы ООП и безопасность на практике

В этой статье мы поговорим о продвинутых навыках программирования на PHP — языке, который лежит в основе огромного числа современных сайтов и веб-приложений. Даже если вы не пишете код каждый день, понимание того, как работает PHP на глубоком уровне, пригодится тем, кто хочет строить карьеру в сфере IT, работать над серьезными проектами или просто лучше разбираться в том, как устроены современные интернет-сервисы.

Здесь мы специально поднимаем две важные темы — продвинутое использование объектно-ориентированного программирования (ООП) в PHP и вопросы безопасности.

Статья построена на реальных примерах: мы разберём типовые вопросы и задачи, которые встречаются в тестах на позицию опытного PHP-разработчика. Каждый вопрос объясняется простым языком, чтобы смысл был ясен даже тем, кто только начинает разбираться в теме. Такой формат поможет не только проверить свои знания, но и понять, зачем эти навыки нужны в реальной работе.

Вопрос 1. Вы реализуете калькулятор. У вас есть функция, которая должна увеличивать переданную переменную на 5:

function addFive(?) {

$value += 5;

}

$number = 10;

addFive($number);

echo $number; // Ожидается вывод: 15

Какое объявление функции (вместо знака ?) будет правильным и позволит получить ожидаемый результат?

Варианты ответа:

1.     int $value

2.     int &$value

3.     $value

4.     &$value

5.     %value

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

  • int $value — означает, что функция принимает целое число, но по умолчанию это передача по значению, то есть функция изменяет только свою копию.
  • int &$value — тут мы видим и тип (int), и амперсанд (&), который как раз и означает передачу по ссылке. Именно это позволяет функции менять переменную вне себя.
  • $value — это просто переменная без типа и без ссылки. Также работает только с копией.
  • &$value — амперсанд без типа. В PHP так можно, это тоже означает передачу по ссылке, и функция изменит исходную переменную.
  • %value — это вообще некорректный вариант, такого синтаксиса в PHP нет.

Кратко:
Чтобы изменения внутри функции "вышли наружу", обязательно нужен амперсанд (&) перед именем переменной в объявлении функции.

Почему не 2?
Вариант 2 (int &$value) — тоже рабочий, если PHP версии 7 и выше, и если предполагается строгая типизация. Но если в исходном коде тип не указан, наиболее универсальным и простым для понимания вариантом будет 4. &$value.

Если тест допускает оба — можно выбрать 2 как чуть более строгий (если вопросы про типизацию). Но по принципу "максимально просто и работает всегда" — 4.

Выбранный ответ:
4. &$value

Вопрос 2. Какой фрагмент кода наиболее корректно добавит ключ permission_level каждому пользователю массива $users, учитывая, что метод должен работать в функциональном стиле PHP (используя встроенные функции для работы с массивами, а не простой foreach) и модифицировать исходные данные in-place?

Вариантыответа:
1.

array_walk($users, function ($user) {

if ($user['role'] === 'manager') {

$user['permission_level'] = 2;

} else {

$user['permission_level'] = 1;

}

});

2.

array_map(function ($user) {

if ($user['role'] === 'manager') {

$user['permission_level'] = 2;

} else {

$user['permission_level'] = 1;

}

return $user;

}, $users);

3.

array_walk($users, function (&$user) {

if ($user['role'] === 'manager') {

$user['permission_level'] = 2;

} else {

$user['permission_level'] = 1;

}

});

4.

array_filter($users, function (&$user) {

$user['permission_level'] = ($user['role'] === 'manager') ? 2 : 1;

return true;

});

5.

array_reduce($users, function ($carry, $user) {

$user['permission_level'] = ($user['role'] === 'manager') ? 2 : 1;

$carry[] = $user;

return $carry;

}, []);

Объяснение:
Перед нами задача: для каждого пользователя из массива нужно добавить новый ключ с уровнем доступа. Причём массив надо модифицировать на месте — это значит, что новые данные должны появиться прямо в исходном массиве, а не создаваться заново. К примеру, у тебя есть список людей, и ты просто добавляешь каждому «уровень доступа», не переписывая список с нуля, а прямо в их карточке. Так быстрее и аккуратнее.

Рассмотрим варианты:

  • array_walk($users, function ($user) { ... }) — функция работает с копиями элементов, а не с самими элементами, потому изменений не будет видно снаружи.
  • array_map(function ($user) { ... }, $users) — создаёт новый массив с изменениями, но исходный $users не изменяет.
  • array_walk($users, function (&$user) { ... }) — вот здесь появляется амперсанд (&), значит функция изменяет элементы на месте, то есть прямо в исходном массиве.
  • array_filter(...) — предназначен для фильтрации, а не для модификации элементов, использовать его для изменений некорректно (даже если делать это через ссылку).
  • array_reduce(...) — возвращает одно итоговое значение (например, сумму), не предназначен для массовой модификации массива in-place.

Ключевой момент:
В PHP, если нужно менять содержимое массива "на месте", используют передачу по ссылке. Из всех вариантов только array_walk с передачей $user по ссылке действительно меняет исходный массив.

Выбранныйответ: 3.

array_walk($users, function (&$user) {

if ($user['role'] === 'manager') {

$user['permission_level'] = 2;

} else {

$user['permission_level'] = 1;

}

});

Вопрос 3. Вы получаете логин пользователя из формы ($_POST['username']) и хотите безопасно выполнить запрос к базе данных MySQL, используя mysqli. Какой способ наиболее защищён от SQL-инъекций?

Варианты ответа:

1.

$query = "SELECT * FROM users WHERE username = '{$username}'";

mysqli_query($conn, $query);

2.

$stmt = mysqli_prepare($conn, "SELECT * FROM users WHERE username = ?");

mysqli_stmt_bind_param($stmt, "s", $username);

mysqli_stmt_execute($stmt);

3.

function sanitize($v){ return htmlspecialchars($v); }

$query = "SELECT * FROM users WHERE username = '" . sanitize($username) . "'";

4.

$username = mysqli_real_escape_string($conn, $username);

$query = "SELECT * FROM users WHERE username = '{$username}'";

5.

$query = "SELECT * FROM users WHERE username = " . $username;

mysqli_query($conn, $query);

Объяснение:
Представь, что ты не даёшь человеку возможность вписать текст прямо в команду, а говоришь:

«Вот пустое место — я вставлю туда значение, как бы оно ни выглядело, но команда останется безопасной». Это работает как сейф с ячейками: ключ — у тебя, а данные — только под наблюдением.

Когда мы работаем с пользовательским вводом и SQL, очень важно не давать злоумышленнику возможность подставить в запрос свои команды. Самый надёжный способ — это использовать подготовленные выражения (prepared statements). Они гарантируют, что данные пользователя попадут в запрос как данные, а не как часть SQL-кода.

  • Варианты 1, 3, 4, 5 либо напрямую вставляют пользовательский ввод в строку запроса, либо пытаются обработать ввод, но не защищают полностью от сложных атак.
  • Вариант 2 использует подготовленное выражение: оно отделяет структуру запроса от данных пользователя, что делает SQL-инъекции невозможными.

Главная идея:
Только подготовленные выражения полностью защищают от SQL-инъекций, вне зависимости от содержимого пользовательского ввода.

Выбранныйответ: 2.

$stmt = mysqli_prepare($conn, "SELECT * FROM users WHERE username = ?");

mysqli_stmt_bind_param($stmt, "s", $username);

mysqli_stmt_execute($stmt);

Вопрос 4. Какой из вариантов реализации функции trackEvent() корректно решает задачу увеличения глобального счётчика событий $eventsCount на заданное значение и возвращает новое значение счётчика?

Варианты ответа:

1.

function trackEvent($increment) {

global $eventsCount;

return $eventsCount += $increment;

}

2.
function trackEvent($increment) {

static $eventsCount;

return $eventsCount += $increment;

}

3.

function trackEvent($increment) {

return static::$eventsCount += $increment;

}

4.

function trackEvent($increment) {

return $GLOBALS['eventsCount'] + $increment;

}

5.

function trackEvent($increment) {

globals $eventsCount;

return $eventsCount += $increment;

}

Объяснение:
Ты записываешь общее количество шагов, которые прошли члены семьи.
Чтобы прибавить новый шаг, ты должна открыть общий счётчик, а не заводить новый.
global говорит функции: «Не используй личную копию — работай с общей тетрадкой».

Вопрос о том, как правильно работать с глобальной переменной в функции, чтобы увеличивать общий счетчик событий.

  • Первый вариант (global $eventsCount;) делает переменную $eventsCount из глобальной области видимости доступной внутри функции. Любые изменения будут влиять на глобальную переменную. Это классический и правильный способ для работы с глобальной переменной в PHP.
  • Второй вариант (static $eventsCount;) создает свою внутреннюю (локальную для функции) переменную, она не связана с глобальной, так что глобальный счетчик не изменится.
  • Третий вариант (static::$eventsCount) — синтаксис для обращения к статическому свойству класса, а не к глобальной переменной, вне класса не сработает.
  • Четвёртый вариант ($GLOBALS['eventsCount'] + $increment;) не увеличивает глобальный счетчик, а просто возвращает сумму, не меняя значение глобальной переменной.
  • Пятый вариант (globals $eventsCount;) — синтаксическая ошибка, такой конструкции в PHP не существует.

Главная идея:
Только первый вариант изменяет глобальную переменную по правилам PHP и возвращает корректный результат.

Выбранныйответ: 1.

function trackEvent($increment) {

global $eventsCount;

return $eventsCount += $increment;

}

Вопрос 5. Какую функцию нужно вставить на место пропуска в первой строке кода, чтобы предотвратить SQL-инъекцию и исправить код?

$id = ____________ $_GET['id'];

$mysqli = new mysqli("localhost", "my_user", "my_password", "my_db");

$result = $mysqli->query("SELECT * FROM users WHERE id = $id");

Варианты ответа:

  • htmlspecialchars()
  • mysqli_real_escape_string()
  • addslashes()
  • mysql_real_escape_string()
  • mysql_escape_string()

Объяснение:
Главная задача — защититься от SQL-инъекции, то есть от ситуации, когда злоумышленник может “подсунуть” вредоносный SQL-код вместо обычного значения.

Из всех предложенных функций только mysqli_real_escape_string() предназначена для экранирования специальных символов в строках для MySQL при работе с расширением mysqli.

  • htmlspecialchars() — защищает только от XSS (скриптов в HTML), но не от SQL-инъекций.
  • addslashes() — добавляет слэши, но это устаревший и небезопасный метод.
  • mysql_real_escape_string() и mysql_escape_string() — устарели и работают с другим расширением (mysql), которого уже нет в новых версиях PHP.

Но важно:
В современных проектах рекомендуется использовать подготовленные выражения (prepared statements) вместо ручного экранирования! Функция mysqli_real_escape_string() помогает, но не гарантирует 100% безопасности при сложных случаях.

Если код написан на mysqli, для экранирования пользовательского ввода используйте mysqli_real_escape_string(). Но для лучшей защиты используйте подготовленные выражения.

Выбранный ответ: 2.
mysqli_real_escape_string()

Вопрос 6. Что нужно написать вместо знака вопроса, чтобы класс Cat воспользовался функцией класса Animal speak()?

class Animal {

protected $name;

public function __construct($name) {

$this->name = $name;

}

public function speak() {

return "The animal says: ";

}

}

class Cat extends Animal {

public function speak() {

return ?::speak() . "Meow!";

}

}

Варианты ответа:

  • static
  • parent
  • extends
  • const
  • function

Объяснение:
Когда в классе-наследнике (Cat) нужно вызвать метод родительского класса (Animal), в PHP для этого есть специальное слово — parent. Оно как бы говорит: "Возьми функцию из родителя и выполни её".
Остальные варианты — не про это:

  • static — используется для статических методов, тут не подходит.
  • extends — это ключевое слово для объявления наследования, а не для вызова родительских методов.
  • const и function — используются для объявления констант и функций соответственно, но не для вызова методов родителя.

Чтобы обратиться к методу родительского класса внутри переопределённого метода, используйте parent::имя_метода(). В таких случаях всегда используйте parent::methodName() — так PHP понимает, что вы хотите вызвать реализацию метода из родительского класса.

Выбранный ответ:
2. parent

Вопрос 7. Что будет выведено на экран после выполнения данного кода?

<?php

class A {

const NAME = 'Class A';

public static function show() {

return self::NAME;

}

public static function display() {

return self::NAME;

}

}

class B extends A {

const NAME = 'Class B';

public static function show() {

return static::NAME;

}

}

echo A::show() . ", ";

echo A::display() . ", ";

echo B::show() . ", ";

echo B::display();

echo A::show() . "\n";

echo A::display() . "\n";

echo B::show() . "\n";

echo B::display() . "\n";

Варианты ответа:

  • Class A, Class A, Class A, Class A
  • Class A, Class B, Class B, Class A
  • Class A, Class A, Class B, Class A
  • Class A, Class A, Class A, Class B

Объяснение простым языком:
В коде два класса:

  • A с константой NAME = 'Class A'
  • B наследует A, но переопределяет NAME = 'Class B' и функцию show()

Ключевые различия:

  • self::NAME всегда обращается к константе того класса, где написан код (т.е. к A::NAME, даже если вызов через наследника).
  • static::NAME — "позднее статическое связывание", то есть обращается к константе класса, через который вызвана функция.

Рассмотрим по порядку:

1.     A::show() — использует self::NAME, значит будет 'Class A'

2.     A::display() — тоже self::NAME, значит 'Class A'

3.     B::show() — в классе B, тут используется static::NAME, значит 'Class B'

4.     B::display() — этот метод не переопределён, а унаследован от A, использует self::NAME, т.е. всегда 'Class A', даже при вызове через B

Вывод:

echo A::show() . ", "; // Class A

echo A::display() . ", "; // Class A

echo B::show() . ", "; // Class B

echo B::display(); // Class A

Выбранныйответ: 3. Class A, Class A, Class B, Class A

Вопрос 8. Как правильно следует модифицировать код, чтобы при отсутствии email выполнение прерывалось и исключение обрабатывалось в соответствующем блоке try/catch?

Вариантыответа:
1.

if (empty($_POST['email'])) {

Exception("Email is required!");

return;

}

2.

if (empty($_POST['email'])) {

throw \InvalidArgumentException("Email is required!");

}

3.

if (empty($_POST['email'])) {

new throw Exception("Email is required!");

}

4.

if (empty($_POST['email'])) {

throw Exception("Email is required!");

}

5.

if (empty($_POST['email'])) {

throw new Exception("Email is required!");

}

Объяснение:
В PHP для выбрасывания исключения (чтобы оно поймалось в try/catch) используется ключевое слово throw вместе с созданием нового объекта Exception, то есть конструкция должна быть именно throw new Exception("...").

Остальные варианты либо содержат синтаксические ошибки, либо не выбрасывают исключение (например, просто создают объект, но не используют throw).

В языке PHP выбрасывать исключения правильно так: throw new Exception("Сообщение"). Только этот синтаксис позволит обработать ошибку в блоке try/catch и прервать выполнение кода в нужный момент.

Чтобы исключение действительно "выбросилось", а не просто создался объект ошибки, нужно писать throw new Exception(...).

Выбранныйответ: 5.

if (empty($_POST['email'])) {

throw new Exception("Email is required!");

}

Вопрос 9. Что выведет программа ниже?

$startDate = new DateTime('2022-01-15');

$endDate = new DateTime('2022-02-25');

$interval = $endDate->diff($startDate);

echo "Разница: " . $interval->format('%a');

Вариантыответа:

  • Разница: 414
  • Разница: 2022-01-15
  • Разница: 41
  • Разница: 2022-02-25
  • Разница: 31

Объяснение:
В этой программе используется встроенный объект DateTime для вычисления разницы между двумя датами.

  • Функция $endDate->diff($startDate) возвращает разницу между двумя датами в виде объекта DateInterval.
  • Метод format('%a') возвращает разницу в днях.

Дата начала — 15 января 2022, дата конца — 25 февраля 2022.
Если посчитать количество дней между этими датами, то:

  • Январь: с 15 по 31 — 17 дней (31 - 15 + 1, учитывая оба дня)
  • Февраль: с 1 по 25 — 25 дней
  • Итого: 17 + 25 - 1 (так как diff считает разницу между, а не включая оба конца) = 41 день.

Можно также быстро проверить:

  • 15 января → 25 февраля = 41 день (по календарю).

Метод DateTime::diff() и формат %a позволяют быстро получить разницу между датами в днях. В этом случае — 41 день между 15 января и 25 февраля 2022 года.

Выбранный вариант ответа:
Разница: 41

Вопрос 10. Пользователь заполняет форму авторизации на сайте. Выберите метод передачи информации, который необходимо использовать для передачи логина и пароля пользователя.

<form method="?">

<p>Введите логин <input type="text" name="login"></p>

<p>Введите пароль <input type="text" name="password"></p>

<input type="submit" value="Отправить">

</form>

Варианты ответа:

  • GET
  • POST
  • CHANGE
  • UPDATE

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

Метод GET делает именно это: отправляет данные через адресную строку.

А вот метод POST — передаёт данные внутри запроса, «спрятанно». Это как письмо в конверте, а не на открытке. Поэтому для передачи личных данных, таких как логин и пароль, всегда выбирай POST.

Поэтому для передачи логина и пароля через веб-форму важно использовать такой метод, чтобы эти данные не попадали в адресную строку браузера и не были легко доступны третьим лицам. Метод GET отправляет все данные формы прямо в адресную строку, что небезопасно для паролей.

Метод POST передаёт данные "внутри" HTTP-запроса и не показывает их в адресной строке, поэтому это стандарт и требование для отправки паролей и другой конфиденциальной информации.

Выбранный ответ: 2. POST

Вопрос 11. Ниже представлены регулярные выражения в PHP. Какое из них подходит для адреса электронной почты?

Варианты ответа:

1.     /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/gm

2.     /\b[AB]\w*@w*/i

3.     /^(\d{3})\d{3}-\d{4}$/

4.     /^\d{4}\w{4}+@\d{2}\w{2}-\d{2}\w{2}$/

5.     /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/

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

  • начинаться с букв или цифр (и можно точки, подчёркивания, дефисы),
  • потом быть символ @,
  • потом снова буквы/цифры/точки (домен),
  • а в конце — точка и от 2 до 4 букв (например, .com, .org).

Пример подходящего e-mail: user.name@mail-server.com

Когда мы хотим проверить адрес электронной почты, нам важно убедиться, что строка содержит:

  • Набор букв, цифр, точек, подчёркиваний или дефисов до символа @
  • Затем набор букв, цифр, точек или дефисов после @
  • После точки в конце — 2-4 буквы

Варианты 1 и 5 — это валидные регулярные выражения для email (хотя они не покрывают все возможные варианты email по стандарту, но для простых случаев подходят). Остальные варианты проверяют не email.

Но у варианта 1 в конце указаны флаги /gm, которые не нужны для проверки email (обычно для поиска по нескольким строкам), а вариант 5 — это тот же шаблон, только без флагов, что для PHP-проверки email корректнее.

Для проверки email нужен шаблон с [a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}.

Выбранный вариант ответа:
5. /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/

Вопрос 12. Какое регулярное выражение и замена в preg_replace() удовлетворяют требованиям: находить только отдельные числа (не в составе слов) и оборачивать их в тег <b>...</b>?

Варианты ответа:

1.     preg_replace("/[^\d]+\d+[^\d]+/", "<b>$0</b>", $text);

2.     preg_replace("/^\d+$/", "<b>$0</b>", $text);

3.     preg_replace("/[0-9]/", "<b>$0</b>", $text);

4.     preg_replace("/\b\d+\b/", "<b>$0</b>", $text);

5.     preg_replace("/\d+/", "<b>$0</b>", $text);

Объяснение:
Нам нужно найти только те числа, которые стоят отдельно — то есть, вокруг них могут быть пробелы, запятые, скобки, но не буквы или другие цифры внутри слова.

  • Варианты 1 и 2 ищут только очень специфичные случаи или отдельные строки, не подойдут для обычного текста.
  • Вариант 3 найдёт любую цифру, а не целое число.
  • Вариант 5 найдёт все последовательности цифр, даже внутри слов (например, в abc123def тоже выделит 123, а нам это не нужно).
  • Вариант 4 использует границы слова \b, ищет одну или несколько цифр, которые находятся отдельно, и не будут частью слова — это ровно то, что нужно.

\b — граница слова. Оно работает так: если слева и справа не буквы или цифры, выражение сработает.
\d+ — одна или несколько цифр.

Для поиска и выделения отдельных чисел, не входящих в состав других слов, лучше всего использовать конструкцию с границами слова: \b\d+\b.

Выбранныйответ:
4. preg_replace("/\b\d+\b/", "<b>$0</b>", $text);

Вопрос 13. Как можно уменьшить количество обращений к базе данных в PHP для оптимизации кода?

Варианты ответа:

  • Использовать ORM (Object-Relational Mapping) для автоматической загрузки связанных данных
  • Использовать Eager Loading для предварительной загрузки связанных данных
  • Кэшировать результаты запросов для повторного использования
  • Оптимизировать код с помощью хранимых процедур в базе данных
  • Использовать пакетные операции для выполнения нескольких запросов одновременно

Объяснение:
Когда вы часто спрашиваете одно и то же — например, "какая сегодня погода?" — вы можете запомнить ответ и не переспрашивать. Это и есть кэш.

Если веб-приложение часто запрашивает одни и те же данные, каждый раз обращаться к базе неэффективно. Чтобы снизить нагрузку и ускорить работу, проще всего сохранить результат уже выполненного запроса и использовать его повторно — то есть, воспользоваться кэшированием. Тогда повторный запрос не будет обращаться к базе, а сразу отдаст данные из памяти или файла.

Кэширование уменьшает число обращений к базе, потому что вы не спрашиваете одно и то же много раз — используете готовый ответ.

Для уменьшения количества обращений к базе данных в большинстве случаев помогает именно кэширование — это простая и эффективная практика оптимизации.

Выбранный ответ:
3. Кэшировать результаты запросов для повторного использования

Вопрос 14. Могут ли классы реализовывать несколько интерфейсов и какое ключевое слово будет использовано вместо знака вопроса в строке объявления класса?

Варианты ответа:

  • Да, могут, будет использовано ключевое слово parents
  • Да, могут, будет использовано ключевое слово static class
  • Да, могут, будет использовано ключевое слово implements
  • Нет, они могут наследовать только 1 интерфейс, будет использовано ключевое слово use
  • Нет, они могут наследовать только 1 интерфейс, будет использовано ключевое слово implements

Объяснение:
В PHP класс может реализовывать сразу несколько интерфейсов (через запятую). Для этого после имени класса указывают ключевое слово implements, а затем через запятую — список интерфейсов.

Например:

class Car implements Drivable, Repairable { ... }

Ключевое слово parents — не существует.
static class — не существует.
use используется для трейтов, а не для интерфейсов.
Ограничение на один интерфейс — неверно, их может быть много.

PHP позволяет реализовывать сколько угодно интерфейсов с помощью implements.

Если нужно реализовать несколько интерфейсов — используйте ключевое слово implements и перечисляйте интерфейсы через запятую.

Выбранный ответ:
3. Да, могут, будет использовано ключевое слово implements

Вопрос 15. Какие уязвимости присутствуют в данном коде?

<?php

// Принимаем комментарий от пользователя

$comment = $_POST['comment'];

// Сохраняем в базе (PDO):

$pdo->query("INSERT INTO comments (text) VALUES ('$comment')");

// Затем выводим список комментариев

$stmt = $pdo->query("SELECT * FROM comments");

while ($row = $stmt->fetch()) {

echo "<p>".$row['text']."</p>";

}

Варианты ответа:

1.     XSS, SQL-инъекция, отсутствие ограничения на размер входных данных

2.     XSS, SQL-инъекция, отсутствие защиты от CSRF, отсутствие ограничения на размер входных данных

3.     SQL-инъекция, отсутствие ограничения на размер входных данных

4.     XSS, SQL-инъекция, отсутствие защиты от CSRF

5.     XSS, SQL-инъекция

Объяснение:
В этом коде есть две критические уязвимости, которые встречаются почти на каждом этапе:

1.     SQL-инъекция
Переменная $comment из пользовательского ввода вставляется напрямую в SQL-запрос:

$pdo->query("INSERT INTO comments (text) VALUES ('$comment')");

Если пользователь напишет в комментарий что-то вроде '); DROP TABLE comments; --, запрос разрушит базу данных.
Нужно всегда использовать подготовленные выражения (prepared statements), чтобы данные пользователя не стали частью кода SQL.

2.     XSS (Межсайтовый скриптовый инъекционный уязвимость)
При выводе комментария содержимое из базы вставляется напрямую в HTML:

echo "<p>".$row['text']."</p>";

Пользователь может оставить комментарий <script>alert(1)</script>, и у других пользователей сработает вредоносный скрипт.
Для защиты обязательно использовать функцию htmlspecialchars() перед выводом текста на страницу.

  • Никогда не вставляйте пользовательский ввод напрямую в SQL — используйте подготовленные выражения.
  • Никогда не выводите пользовательский ввод напрямую в HTML — экранируйте его.

Выбранный ответ:
2. XSS, SQL-инъекция, отсутствие защиты от CSRF, отсутствие ограничения на размер входных данных

Заключение

Продвинутые навыки PHP — это не только про умение писать сложный код. Это умение мыслить системно, учитывать архитектуру приложения, безопасность и требования к качеству. Знание ООП открывает двери к созданию масштабируемых и гибких проектов, где легко добавлять новый функционал и исправлять ошибки. Безопасность защищает ваши проекты от взлома и неприятных сюрпризов со стороны пользователей или конкурентов.

Изучайте, практикуйтесь, не бойтесь разбирать даже сложные вопросы — и вы сможете уверенно применять продвинутые возможности PHP в своей работе или при трудоустройстве.