Задать вопрос
Ответы пользователя по тегу PHP
  • Экранирование sql запросов, достаточно ли функции?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Эта функция не самописная, а цельнотянутая. Причем из самых смрадных помоек интернета.
    К защите от инъекций не имеет вообще никакого отношения.

    - trim() ни к инъекциям, ни к защите отношения не имеет
    - stripslashes() просто бессмысленная функция, которая только портит данные
    - htmlspecialchars() не имеет отношения к SQL. Применяется при выводе данных, а не при получении
    - real_escape_string() - единственная функция, которая имеет отношение к SQL, но при этом вообще не предназначенная ни для каких защит.

    Попробуйте на основании этой информации самостоятельно оценить полезность вашей функции.

    Возьмем классический пример
    $_GET['id'] = '1;DROP TABLE Students;';
    $id = formatstr($_GET['id']);
    $sql = "SELECT * FRPM Students WHERE id=$id";

    И посмотрим, помогло ли ваше самописное экранирование предотвратить хоть что-то (спойлер: нет).
    (да, та функция, которую лично вы используете для выполнения запросов, выполняет только один запрос за раз. И вместо инъекции вы получите ошибку. Но это не отменяет сам факт получения инъекции.)

    Как правильно все отметили выше, для защиты от инъекций используются подготовленные запросы, в которых все переменные заменяются на специальные маркеры, и передаются отдельно.
    Ответ написан
    2 комментария
  • Как исправить кракозябры вместо кириллических символов при записи переменной в сессию?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    При соединении с БД надо указывать кодировку.
    Поскольку на сайте используется юникод, то соответственно указывать надо utf8mb4

    А сессии тут вообще не при чем.
    Ответ написан
    1 комментарий
  • Как грамотно сделать обработку ислючений в php?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Структура проекта какая-то странная, в нем, судя по всему, контроллером называется роутер, но на этом я останавливаться не буду, обработка исключений от этого не зависит.

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

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

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

    2. Системные ошибки обрабатывает глобальный обработчик исключений, логируя саму ошибку, а на клиент отправляя статус 500 и какое-нибудь абстрактное сообщение о проблеме на сервере. Это самое важное в системных исключениях - текст ошибки никогда, не при каких обстоятельствах не уходит наружу.

    Пример такого исключения - когда запрос в БД порождает ошибку.

    3. Пользовательские исключения, там где они нужны, обычно обрабатываются по месту, но есть нюансы.
    Собственно, под обработкой исключений часто понимают два разных действия:
    - собственно обработку, когда программа совершает какие-то действия, чтобы нивелировать негативный эффект. Например, если не удалось подключение к какому-то сервису, то либо попробовать подключиться к другому, либо просто подождать и попробовать снова.
    - простое информирование пользователя

    Для информирования можно действительно ловить исключение через try..catch и писать какое-то свое сообщение.
    Но можно и автоматизировать этот процесс, вот две статьи, которые показывают примеры, как это можно сделать:
    https://angelovdejan.me/2022/11/24/centralized-exc...
    https://habr.com/ru/articles/688202/
    Ответ написан
    Комментировать
  • Почему не записываются данные в БД, UPDATE не работает?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Если данные не обновляются в БД, то на это может быть три причины
    1. Код добавления вообще не вызывался.
    2. При добавлении произошла ошибка.
    3. Данные добавились, в одну базу, а результат смотрим в другой.

    Первый пункт должен проверить сам программист, никакой дядя с хабра за него это не сделает.
    Второй в случае Ларавля отпадает, там, в отличие от самопальных поделок горе-программистов, с отображением ошибок все хорошо.
    Остается только третий.
    Ответ написан
    Комментировать
  • Как правильно распределять ответственность между классами?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Сервис - это часть модели. Класс, который реализует некую бизнес-логику, не связанную напрямую с хранилищем.
    Репозиторий - часть модели, класс, который содержит методы для работы с хранилищем.
    Стор - это какая-то местечковая приблуда. Из битрикса небось? Либо какие-то академические измышления, вот как в соседнем ответе. Вроде человек что-то пишет, но яснее ничего не становится.
    Маппер имеет много значений. Если речь про Data Mapper, то это разделение самих данных и их отображения в БД.

    Читать стандартно - Дядюшка Боб Мартин и Мартин Фаулер. Конкретно по РНР - Зандстра.
    Ответ написан
    2 комментария
  • Как исправить кракозябры вместо кириллических символов?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    У вас в этой таблице лежат данные, закодированные в windows-1251.
    Вам надо или перекодировать полученные данные нормально, через mb_convert_encoding(), а не вот это вот недоразумение utf8_encode.

    Или лучше всего один раз и навсегда перекодировать данные в БД.
    Для этого надо сделать дамп, поменять в нем set names с utf8 на cp1251, и выполнить сначала на тестовой БД
    Если данные будут читаться, то выполнить на боевой.
    Ответ написан
    1 комментарий
  • Как работает htmlspecialchars()?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Ну уж для такой-то простой функции должно быть достаточно написанного на странице документации.
    И, в частности, про ENT_QUOTES ;)

    Эта функция заменяет спецсимволы, которые являются управляющими в контексте HTML, на безобидные HTML-сущности. Вот и всё.
    Чисто визуально управляющие символы будут выглядеть так же, как и должны, но в коде не будут представлять ни малейшей опасности.

    Для XSS важно, будут ли экранироваться одинарные кавычки или нет, если одинарные кавычки используются для ограничения атрибута.
    Пример

    <?php $name = "' onclick='alert(\"pwned!\")"; ?>
    <input value="<?=htmlspecialchars($name) ?>"> Не важно. Весь текст так и заключен в двойные кавычки
    <input value='<?=htmlspecialchars($name) ?>'> Важно. Закрыли одинарную, вписываем, что хотим

    Во втором случае этот код отрендерится, как
    <input value='' onclick='alert(&quot;pwned!&quot;)'>
    (но при этом &quot; отрендерятся в двойные кавычки, и в onclick будет уже валидный яваскрипт
    alert("pwned!")

    А с взведенным флагом ENT_QUOTES мы получим
    <input value='&#039; onclick=&#039;alert(&quot;pwned!&quot;)'>

    И хотя значение внутри атрибута отрендерится, как ' onclick='alert("pwned!"), но оно все целиком будет внутри атрибута value.

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

    К счастью, РНР уже позаботился о вас и ваших друзьях. Начиная с версии 8.1, флаг ENT_QUOTES ставится по умолчанию. И приведенный выше пример будет работать только на устаревших версиях. Так что ручное добавление уже уходит в область легаси-практик.

    Чтобы не ломать пальцы, каждый раз набирая всё это htmlspecialchars ENT_QUOTES, толковые джуны всегда пишут пользовательскую функцию-макрос, типа
    function esc($var) {
        return htmlspecialchars($var, ENT_QUOTES);
    }

    Правда, надолго с этой функцией не задерживаются, потому что она годится только при обучении. А любой проект сложнее, чем домашняя страничка про любимого котика, в обязательном порядке уже должен для вывода использовать специальный шаблонизатор, например Twig. Где весь вывод идет с помощью специального оператора, который уже по умолчанию экранирует HTML
    <input value="{{$name}}"> Не важно. Все уже проэкранировано до нас

    Важно помнить, что это экранирование работает только в контексте HTML.
    Если выводим данные внутри кода яваскрипт, то htmlspecialchars поможет как мертвому припарки. И в этом случае надо использовать json_encode.

    Ну и разумеется, надо не забывать комбинировать все варианты экранирования.
    Например, код яваскрипт, который пишется в атрибут HTML тега, надо весь целиком экранировать c с помощью htmlspecialchars. А данные для этого кода - c с помощью json_encode:
    <button onclick="<?= htmlspecialchars("window.open(".json_encode($name).")", ENT_QUOTES) ?>">
    Ответ написан
    3 комментария
  • Почему не происходит изменение записи?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    4 комментария
  • Зачем для кеширования использовать Redis, если можно сделать файловое кеширование?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Вы очень узко понимаете понятие кэширования.
    И забываете о том, что из кэша не только читают, но в него еще и пишут.
    в .php файлах эффективнее хранить не кэш, а какую-то статичную информацию, которая редко изменяется (скажем, только при деплое), и при этом общую для всех пользователей. При соблюдении этих условий кэширование в .php файлах вполне себе используется.

    Но понятие кэша гораздо шире. Кэшироваться может и специфичная для конкретного пользователя или запроса информация. В этом случае никаких .php файлов не напасешься.

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

    Еще одна причина, не главная, но все равно важная - масштабирование. Один сервис редиса можно использовать с несколькими инстансами РНР. Плюс сам редис можно масштабировать на несколько физических инстансов.
    Ответ написан
    Комментировать
  • Как вывести нужное значение функция substr_count?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    В этом примере три переменных содержат по одной букве "в". поэтому этот код выводит 3.
    Чтобы вывести 1, надо убрать букву "в" из содержимого двух переменных

    Если надо узнать, содержимое какой переменной равно в, то тогда это и проверять, без всяких substr_count

    $count_a = $id1 === "в";
    Ответ написан
  • Почему не срабатывает тернарный оператор?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Есть конструкция которая выдает предупреждение Notice: Undefined index: priceweek in ...

    Это неправда.
    Ответ написан
    3 комментария
  • Почему может создаваться большое количество файлов сессий на сервере Ubuntu?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Сессии сами не создаются, их создает РНР при обращении к сайту.
    Сколько обращений - столько сессий.

    Соответственно, смотреть надо по двум направлениям
    1. проверить, не долбит ли кто-то запросами, и заблокировать или ограничить, например использовать Rate Limit у NGINX
    2. файловая система - самое убогое хранилище для сессий, и используется по умолчанию только потому, что для всех других нужно указывать параметры подключения. Соответственно, вместо файлов использовать базу данных.

    Плюс надо смотреть по коду. Если сайт создает сессию на откровенно мусорный запрос, то, возможно, этого не стоит делать. Ну и нельзя сбрасывать со счетов гениальных скриптописателей, которые сами себе создают нагрузку на сайт. Не раз и не два я видел любителей инклюдить локальные файлы по HTTP.
    Ответ написан
    Комментировать
  • Как перехватить все существующие типы исключений?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Если вы тупо не знаете, какие бывают типы исключений, то это легко исправить, посмотрев в документации.
    Там написано, что объекты исключений имеют иерархическую структуру, и самым общим предком является интерфейс Throwable.

    Отдельно следует отметить, что за использование try catch чтобы тупо написать "Получена ошибка" надо бить по рукам. Так никогда не надо делать. Это глупо и бессмысленно. РНР и сам прекрасно выведет и $e->getFile(), и $e->getLine(), и даже $e->getMessage(), и помогать ему в этом не надо.

    Исключение надо ловить только тогда, когда есть определенный сценарий обработки. Например, в случае, если test() выполнилась с ошибкой, то вызвать test2().
    Но главное - внутри блока catch ничего не выводить пользователю.
    Общением с пользователем должны заниматься только специально предназначенные для этого блоки программы. А не любая строчка, которой вдруг захотелось это сделать.

    Поэтому в данном конкретном случае ваша задача еще больше упрощается - не надо ловить вообще никакое исключение.
    Ответ написан
  • Как посчитать сумму позиций из 2-х источников?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    В общем случае избежать "цикла в цикле" позволяет использование индексации массива идентификатором и - соответственно - обращение по индексу вместо перебора.
    В данном случае вторую коллекцию проиндексировать можно с помощью метода keyBy('id');

    И тогда останется только один цикл по первому массиву, в котором обращаться ко второму по id.
    То есть сложность будет длина массива * 2 вместо длина массива * длина массива

    Если же данные берутся из базы, то, как правильно заметили в комментариях, там же и надо сразу считать

    spoiler
    Типичный, кстати, вопрос из серии "Выучил ларавель, не выучил программировать"
    Ответ написан
    7 комментариев
  • Почему не удается получить доступ к смещению типа string в строке?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Потому что у строки не может быть строкового смещения.

    В ответе выше написана чушь.
    Обращаться к строке по индексу можно. И в РНР7, и в РНР 8. Без всяких предупреждений.
    К отдельным байтам (но не символам) в строке можно обращаться, указывая смещение в квадратных скобках:
    echo 'hello'[0];
    Но в сообщении об ошибке говорится конкретно про строковые ключи. А это уже действительно бессмыслица, никаких строковых смещений в строке быть не может. Что и написано черным по белому в ошибке.

    И проблема действительно находится выше, в совершенно дурацком присвоении пустой строки массиву.
    Если мы хотим использовать массив, то и присваивать начальное значение надо в виде пустого массива, а не строки.

    При этом отдельно проверять наличие $aUnit[$server['unit']] и $aUnit[$server['unit']][$server['game']] не нужно. Достаточно проверить на существование сразу конечный элемент.
    В 8.2 это можно красиво написать одной строчкой
    $aUnit[$server['unit']][$server['game']] ??= '';
    Но если почитать код дальше, то станет видно, что и здесь нам строка тоже не нужна. А снова нужен массив.
    Потому что в коде ниже эта строка разбивается через explode(???).
    То есть, надо написать
    $aUnit[$server['unit']][$server['game']] ??= [];
    $aUnit[$server['unit']][$server['game']][] = $server['id'];
    А ниже выкинуть всё от explode() до unset()
    spoiler
    Вообще, складывается ощущение, что исходный код писал либо ребенок, либо шизофреник - две разные личности, которые не видят код друг друга. Вот как с этим массивом например.
    Или вот этот кусок кода еще мне очень понравился
    $sql->query('SELECT `id` FROM `servers` LIMIT 1');
    if(!$sql->num())
        return NULL;
    $sql->query('SELECT `id`, `unit`, `game` FROM `servers` ORDER BY `unit` DESC');
    $all = $sql->num();

    Напоминает анекдот про ирландца, который перед тем как поспорить, выпьет ли он 10 кружек пива, сначала сбегал в соседний паб. Проверить, выпьет ли он 10 кружек пива.
    Ответ написан
    Комментировать
  • Используется ли шаблонизатор в проде?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    И так и так.
    Чаще всего используется традиционный рендеринг на бэке, но если кто хочет помоднявее, или чтобы не тратиться на отдельные версии для мобильного приложения, то делают рендеринг на фронте.

    Как пользователь, я ненавижу рендеринг на фронте, на практике он ВСЕГДА тормозит.
    За примером далеко ходить не надо, это новый дизайн Хабра.

    Вы, кстати, неправильно понимаете термин "прод".
    Продакшен окружение относится не к коду, а к тому, где он выполняется
    В локальном окружении разработчик пишет и отлаживает программу
    Тестовое (стейдж) окружение полностью имитирует боевой сервер, но доступно только разрабочикам/тестировщикам/заказчикам
    И, наконец, продакшен - собственно рабочий сервер, который находится под реальной нагрузкой
    А код везде один и тот же.
    Ответ написан
    3 комментария
  • Как превратить текст в исполняемый код?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    В ответе выше написана чушь.
    Написать такой парсер можно за 10 минут.
    Плюс готовых парсеров формул в интернете - как грязи.
    И если на курсах не совсем идиоты, то их интересует именно написание парсера, а никакой не eval.
    Ответ написан
  • Где ошибка в коде и не происходит авторизация с password_verify?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Возможные причины
    1. В БД нет такого пользователя.
    2. Недостаточная длина поля под хэш.
    3. Ошибка при выполнении запроса.
    4. При регистрации в базу вместо нормального хэша пишется какая-то ерунда.
    Ответ написан
  • Почему не выводит ошибку при работе с базой после переноса на сервер?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Комментировать
  • Как правильно выводить данные php?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Специально для тех, кого интересует этот животрепещущий вопрос и персонально для Максим Ткачев.

    print_r($result,true); - это бред обкуренного жирафа. Эта строчка не выводит вообще ничего.
    Про функцию print_r надо забыть, она была нужна только в 4 версии РНР.

    Чтобы вывести любые данные, полученные курлом, надо использовать обычное echo
    Предварительно задав CURLOPT_RETURNTRANSFER
    А если что-то ещё не выводится, или выводится не так, то надо смотреть, где ещё в коде написан бред.
    Ответ написан
    Комментировать