Задача была такова: Есть общая папка с изображениями, к каждому из которых проставлены ключевые слова (определяющие конкретного человека/организацию). Необходимо получать перечень изображений с определенным тегом для последующего вывода галереи на странице этого человека.

Просьба для тех, кто будет читать, особенно с целью критиковать
Основная задумка статьи, помочь новичкам типа меня настроить один из частных случаев взаимодействия exif-php-mysql-php-html.
Я за объективную критику, если у вас есть рациональные предложения по улучшению кода, я с радостью их послушаю и приму к сведению.
Еще раз напомню, что это чуть ли не первое моё написание php-скрипта, где необходимо работать с БД на сервере, поэтому у меня действительно по-тупому всё сделано в одном скрипте.


Просьба не судить очень строго, я лишь начинающий в php и mysql, но основная проблема и цель написания данной статьи в том, что в поисках решения своей проблемы я перерыл очень много форумов и сайтов, но адекватного ответа так и не нашел.

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

Был выработан следующий алгоритм действий:

  1. Получить список файлов (*.jpeg);
  2. Циклом пройти по всем файлам и вытащить exif данные (а именно тег keywords);
  3. Занести имена файлов и ключевые слова в базу данных;
  4. Получение списка файлов по определенному тегу;
  5. Самое приятное — исправление ошибок.

Что происходит дальше пока не сильно важно, потому что основные проблемы родились именно на пункте 4.

Итак, разберемся что у нас происходит на каждом этапе:

1. Получить список файлов (*.jpeg)


Тут особо проблем не возникло, спасибо функции glob();

$filelist = glob("*.jpg"); // получаем список файлов (скрипт лежит в папке с файлами)

2. Вытащить exif данные (тег keywords)


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

foreach ($filelist as $name) { // Перебираем файлы, имена заносим в переменную $name
    $exif = exif_read_data($name, 0, true); // Получаем метаданные из файла
    foreach ($exif as $key => $section) { // Циклично получаем все теги метаданных
      foreach ($section as $setting => $val) { // Получаем значение тега
        if ($key == "IFD0" && $setting == "Keywords") { // Ждем когда доберемся до тега keywords
          echo "$key.$name: $val<br />"; // Выводим полученное значение
        }
      }
    }
}

3. Занести полученную информацию в базу данных


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

Нам необходимо:

  • Подключиться к БД

     $con = mysqli_connect('<server_name>','<user_login>','<user_pass>', '<database>') or die ("html>script language='JavaScript'>alert('Не удается подключиться к базе данных. Повторите попытку позже.'),history.go(-1)/script>/html>"); // Подключение к БД с выводом ошибки, если подключение не удалось
    

  • Создать таблицу (если её нет)
    Я использовал поля Number (порядковый номер/id), filename (имя файла оригинального изображения) и keywords (ключевые слова)

    $sql_ins_first = "CREATE TABLE IF NOT EXISTS test (id int(11) NOT NULL AUTO_INCREMENT, filename varchar(255) CHARACTER SET utf8 NOT NULL, keywords varchar(255) CHARACTER SET utf8 NOT NULL, PRIMARY KEY (id)) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1"; // Создание таблицы (если её нет)
    mysqli_query($con, $sql_ins_first); // Отправить запрос, который описан выше
    

  • Добавление записей в таблицу

    $sql_ins_second = "INSERT INTO <table_name> (filename, keywords) VALUES ('$name', '$val')"; // Запрос на добавление записей в таблицу (на каждой итерации цикла)
    if (mysqli_query($con, $sql_ins_second)) { // Проверка выполнения запроса
        echo "$name - New record created successfully<br />"; // Информация успешно занесена в базу
    } else {
        echo "Error: " . $sql_ins_second . "<br>" . $con->error; // Ошибка записи данных в БД
    }
    

4. Получение списка файлов по определенному тегу


Здесь начинается самое интересное, потому что именно отсюда мы плавно втекаем в ошибки, которые устраняются не совсем очевидным образом

Привожу сразу пример моей функции с комментариями

function select_sql($key) // На вход подаём тег, который нам нужен для вывода конкретного набора файлов 
{
    /* Запрос: Выбрать поле "filename" из таблицы "test",
    где символьная строка "keywords" соответствует шаблону '%$key%' */
    $sql_select = "SELECT filename FROM test WHERE keywords LIKE '%$key%'";
    $result = mysqli_query($con, $sql_select); // Заносим результат в переменную

    /* извлечение ассоциативного массива */
    while ($row = $result->fetch_assoc()) { // Обрабатываем все полученные записи в результате нашей выборки
        echo "<div><p>".$row['filename']."</p><img src=\"".$row['filename']."\" style=\" width: 200px\" /></div>"; // Вывод на страницу имени файла и превью нашего изображения
    }
}



А перед тем, как мы перейдём к следующему пункту, я скажу, что если собрать этот код воедино, то мы получим проблему (возникает не у всех, если сервер настроен правильно со всех сторон, но это не точно), которая решается не совсем тривиально (как мне кажется).

У меня получилась следующая ситуация.

Полученная строка ключевых слов к изображениям в формате ASCII, подаваемая на вход переменная с нужным тегом также в формате ASCII, но при отправке запроса (не важно, через скрипт или через PhpMyAdmin) на выходе получаем NULL.

Попробовав множество вариантов (среди них обязательно был вариант с использованием utf8_encode(), который, кстати, не работал).

5. Самое приятное — исправление ошибок


Первое, что предлагает нам интернет и люди, не до конца разобравшиеся с документацией, это проверить «А склеили ли вы переменную с запросом?», «А нет ли у вас лишних пробелов?», «А правильно у вас собирается запрос?», «А что у вас за БД?» и т.д. т.п

По факту, мы просто получили ситуацию, когда всё работает (теги записались в БД, запрос собирается правильно, но на выходе мы всё равно имеем пустую выборку).
Логически приходим к выводу, что проблема в кодировке… Но что делать, если стандартная функция utf8_encode() не работает?

Скажу честно, я перепробовал безумно много вариантов, но в итоге у меня родилась идея «с промежуточной кодировкой» — я сначала попробовал перевести из ASCII в другую кодировку, а потом уже в UTF-8, но снова ничего не получалось.
Здесь я почуял неладное, а есть ли какой-то список кодировок и есть ли у меня там что-либо?

Немного погуглив, нашел и попробовал установить в начале скрипта mb_detect_order(«eucjp-win,sjis-win,UTF-8»); и сразу же получил кодировку всех строк в eucjp-win. Осознав, что можно попробовать переставить utf-8 на первое место, у меня всё пошло как по маслу, но было очень важно после считывания метаданных из картинок — экранировать символы.

Представляю вашему вниманию итоговый скрипт:

<?php
  mb_detect_order("UTF-8,ascii,eucjp-win,sjis-win"); // Установка списка кодировок

  $con = mysqli_connect('<server_name>','<user_login>','<user_pass>', '<database>') or die ("html>script language='JavaScript'>alert('Не удается подключиться к базе данных. Повторите попытку позже.'),history.go(-1)/script>/html>"); // Коннект к БД

  $sql_ins_first = "CREATE TABLE IF NOT EXISTS test (id int(11) NOT NULL AUTO_INCREMENT, filename varchar(255) CHARACTER SET utf8 NOT NULL, keywords varchar(255) CHARACTER SET utf8 NOT NULL, PRIMARY KEY (id)) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1";
  mysqli_query($con, $sql_ins_first); // Проверка наличия/создание таблицы

  $filelist = glob("*.jpg"); // Получение списка файлов в директории с расширением *.jpg

  foreach ($filelist as $name) { // Цикл по файлам
    $exif = exif_read_data($name, 0, true); // Считывание метаданных
    foreach ($exif as $key => $section) { // Цикличный проход по тегам
      foreach ($section as $setting => $val) { // Получение значения тега
        if ($key == "IFD0" && $setting == "Keywords") { // Проверка соответствия нужного нам тега (keywords)
          $val1 = mysqli_real_escape_string($con, $val); // Экранирование символов в полученной строке значений тега Keywords
          $val1 = str_ireplace(";", "; ", $val1); // Заменяем ";" на "; "
/* При проверке текущего состояния строки после экранирования
мы увидим между всеми символами "\0",
а это значит, что нам необходимо их удалить,
чтобы получить адекватную строку */
          $val1 = str_ireplace("\\0", "", $val1); // Заменяем "\0" на "" (пустую строку) и не забываем экранировать слеш

          $sql_ins_second = "INSERT INTO test (filename, keywords) VALUES ('$name', '$val1')"; // Запрос на добавление записей в БД

/* Проверка записи данных в БД */
          if (mysqli_query($con, $sql_ins_second)) {
               echo "$name - New record created successfully<br />";
          } else {
               echo "Error: " . $sql_ins_second . "<br>" . $con->error;
          }
        }
      }
    }
  }
$con->close(); // Закрыть подключение

/* Объявление функции select_sql() */
function select_sql($key)
{
  $con = mysqli_connect('<server_name>','<user_login>','<user_pass>', '<database>') or die ("html>script language='JavaScript'>alert('Не удается подключиться к базе данных. Повторите попытку позже.'),history.go(-1)/script>/html>"); // Коннект к БД
  $sql_select = "SELECT filename FROM test WHERE keywords LIKE '%$key%'"; // Запрос на выборку данных по определенному тегу
  $result = mysqli_query($con, $sql_select); // Заносим результат в переменную

  while ($row = $result->fetch_assoc()) { // извлечение ассоциативного массива
    echo "<div><p>".$row['filename']."</p><img src=\"".$row['filename']."\" style=\" width: 200px\" /></div>"; // Вывод названий файлов и изображений на страницу
  }
$con->close(); // Закрыть подключение
}
$string_key = utf8_encode('test'); // Преобразование переменной в кодировку UTF-8 (на всякий случай)
select_sql($string_key); // Вызов функции с нужным тегом
?>

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



И, как говорится:
Спасибо за внимание! Я готов выслушать ваши вопросы!

Комментарии (60)


  1. t3chn0ph0b
    30.11.2021 16:44
    +1

    mysqli_connect? Серьезно?


    1. t3chn0ph0b
      30.11.2021 17:16
      -1

      Дополню, а то выглядит коммент не очень :)

      Если вы пишете для новичков, то приучайте их сразу не писать на PHPк хорошему. Как минимум использовать PDO для работы с БД.


      1. LearnPC Автор
        30.11.2021 18:12

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

        Спасибо за обратную связь


      1. SerafimArts
        30.11.2021 18:59
        +6

        С каких пор PDO стал чем-то хорошим? Это два разных инструмента для разного вида задач. В том же PDO нет поддержки асинхронных запросов (в отличие от mysqli), например.


        Я уж не говорю о том, что PDO — забагованный (проверял на PHP 8.0 + 8.1) и содержит кучу косяков, начиная с очевидных, вроде флагов PDO::ATTR_ERRMODE, который работает только в том случае, если его вызвать через setAttribute (при передаче через конструктор — не срабатывает в препейрах), заканчивая поломанной работой с транзакциями, если выставлен флаг PDO::ATTR_CASE (казалось бы, как это связано, но как бы вот).


        А если говорить о mysqli, то он хоть и прибит гвоздями к одной БД, но зато гибче и надёжнее.


        1. FanatPHP
          01.12.2021 18:56
          +1

          А можно какие-то пруфы всем этим заявлениям?

          У всего мира PDO::ATTR_ERRMODE прекрасно работает в конструкторе.

          Асинхронность же мускулявых запросов сильно преувеличена. Учитывая, что запрос в любом случае может быть только один, какой-то разумный вариант применения этой асинхронности в РНР мне как-то не приходит в голову.


          1. SerafimArts
            01.12.2021 23:27

            А можно какие-то пруфы всем этим заявлениям?
            У всего мира PDO::ATTR_ERRMODE прекрасно работает в конструкторе.

            Если говорить про реальные пруфы, которые прям вот сейчас можно взять и проверить, то вот тут: ExecutableQuery.php#L86 не будет возникать никаких исключений, если убрать вот эту строчку: Connection.php#L30 Сама опция ATTR_ERRMODE в конструктор, если что, передаётся: Отсюда Driver.php#L49 вот сюда Driver.php#L52


            Это всё на sqlite + php 8.1 воспроизводится 146%. Именно на этом кейсе столкнулся. Если неудобно через git clone проверить и убедиться, то могу попробовать собрать какой-нибудь гист попроще, без всякого лишнего кода.


            P.S. Но вообще выглядит какая-то магия, судя по вашей ссылке, т.к. я не копал глубже, проставил дополнительно setAttribute, оно всё починилось и я забил.


            1. FanatPHP
              01.12.2021 23:32
              +1

              Но я же выше уже сделал такой гист. Вот он же с синтаксической ошибкой. Всё прекрасно бросается. Что я делаю не так?


              1. SerafimArts
                02.12.2021 00:11

                Да, всё верно. Проблема была именно в синтаксической ошибке. Более того — не имеет смысла даже запускать тот код, что выше по моим ссылкам — там тоже не воспроизводится больше. Не знаю с чем было связано, возможно из-за того что я что-то проглядел, возможно из-за того что это был PHP 8.1 RC что-то там, но саму строчку с setAttribute я спецом коммитил, чтоб пофиксить конкретно эту проблему.


                А на счёт ATTR_CASE + транзакций я уже забыл кейс. roxblnfk можешь напомнить пожалуйста что там было? Я точно помню что я показывал тебе этот магический баг в пдо. И кажется эта проблема была именно при работе с mysql.


                1. roxblnfk
                  03.12.2021 13:29

                  Там было что-то связанное с тем, что при выставлении CASE_NATURAL, PDO считал транзакцию запущенной (метод inTransaction() возвращал true).
                  Мы не документировали этот баг, поэтому можно считать, что его нет :)


      1. FanatPHP
        01.12.2021 19:11
        +1

        Оба коммента выглядят не очень по совсем другой причине.
        Как и многие новички, вы путаете два понятия, драйвер для работы с БД и безопасное составление запросов.

        Сам по себе драйвер не панацея. Точно так же у автора будут те же самые инъекции и с PDO.

        Поэтому свою мысль надо формулировать немного по-другому, не "PDO rulez, mysqli suxx!!!", а "переменные не должны помещаться напрямую в запрос, а передаваться через привязку к подготовленному выражению". А уж через какой драйвер это делается - дело сто тыщ десятое.


        1. LearnPC Автор
          01.12.2021 19:19

          Большое спасибо за ряд комментариев и советов. Приму к сведению


  1. mastacamp
    30.11.2021 16:45
    +3

    1. Почему бы не работать напрямую с $exif['IFD0']['Keywords'] ? судя по коду и $key и $setting являются ключами массива, значит к ним можно получить доступ напрямую, не делая лишних foreach.

    2. Кодировку можно задать после соединения с базой, например так: mysqli_query($con,"SET NAMES utf8mb4_bin"); mysqli_query($con,"SET CHARSET utf8mb4");mysqli_query($con,"SET SESSION collation_connection = 'utf8mb4_bin'");

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

    Ну и сейчас должны пойти комментарии от других комментаторов :)


    1. LearnPC Автор
      30.11.2021 18:11

      Отвечу по пунктам:

      1. Спасибо, учту

      2. Я пробовал что-то подобное, но у меня всё равно выводилось NULL, но я перепроверю

      3. Об этом я написал в конце статьи, что это тупой скрипт для локальной БД для обучения, что по хорошему вынести в отдельный скрипт - понимаю.

      Спасибо за обратную связь!


    1. FanatPHP
      01.12.2021 19:02

      .2.Можно, но не нужно. Для этого есть специальная функция mysqli::set_charset()
      И при этом очень желательно не путать collation и charset.



  1. debagger
    30.11.2021 18:16
    +1

    Я не особо знаю пхп, но вот это вот:

    $sql_select = "SELECT filename FROM test WHERE keywords LIKE '%$key%'";

    выглядит как возможность для sql-иньекции...


    1. LearnPC Автор
      30.11.2021 18:40

      Скажу честно, ещё не изучал защиту от sql-инъекций. Это лишь начало моего пути, прежде чем браться за какой либо проект, я обязательно изучу данный вопрос.

      Спасибо за обратную связь!


      1. FanatPHP
        01.12.2021 19:13

        Это всё равно что написать статью в медицинский журнал про то как сделать операцию пациенту, и при этом писать что "вопрос стерилизации я ещё не изучал".


    1. alex-khv
      30.11.2021 18:59

      Судя по

      <img src="".$row['filename']."" style=" width: 200px" />

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


    1. BigDflz
      30.11.2021 19:09
      -1

      используйте хранимые процедуры и это не будет поводом для боязни инъекций


      1. LearnPC Автор
        30.11.2021 19:14

        Спасибо


      1. alex-khv
        01.12.2021 08:34
        -2

        Хранимые процедуры уменьшают поверхность атаки, но не являются панацеей от инъекций.

        Автору лучше изучить ORM, это на данном этапе развития даст больше чем закапывание в дебри SQL.


        1. FanatPHP
          01.12.2021 18:58
          +1

          Учить ORM не выучив SQL - это стрелять себе даже не в ногу, а сразу в голову.


        1. BigDflz
          02.12.2021 22:18

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


          1. FanatPHP
            03.12.2021 09:34

            Вот именно, что только один вариант и что под каждый запрос свою процедуру. То есть в реальном коде это невозможно будет использовать.

            Подготовленный запрос сделать в сто раз проще и надёжнее.


            1. BigDflz
              03.12.2021 17:17

              люди делают проекты с 1000+ процедурами - и не останавливаются. возможности хранимых процедур намного больше "подготовленных запросов"

              если для Вас невозможно использовать - "Вы проста не умеете их готовить..."

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


              1. FanatPHP
                03.12.2021 17:27
                +1

                :)))

                Как-то вы очень резко перешли от "Мне тут пацаны показали" к "вы не умеете" :)

                Давайте вы будете говорить только на основании собственного опыта. Итак, сколько процедур написали лично вы? Можете показать пример одной?

                А я пока дам вам несколько советов:

                • 1000 запросов - это маленькая домашняя страничка. Не забывайте, что вы начали разговор про процедуры в контексте защиты от инъекций. То есть каждый запрос в приложении должен выполняться через хранимую процедуру.

                • Возможности хранимых процедур действительно больше подготовленных запросов (в кавычки брать совсем не обязательно, это не инопланетная технология), но и поддержка тоже сложнее. Поэтому использовать их для защиты от инъекций - это из пушки по воробьям.

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


                1. BigDflz
                  03.12.2021 19:18
                  -1

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

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

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

                  в ide основного кода нет подсветки кода sql, это строка, обыкновенная строка. а запросы бывают на 1-3 экрана . в ide для sql его автоматом отформатируют, разукрасят, проверят. одно нажатие и выполнение.


                  1. FanatPHP
                    03.12.2021 19:50

                    Я не буду комментировать всю ту многословную чушь, которую вы тут понаписали вместо одного короткого примера кода из вашего "реального" проекта, а просто порекомендую вылезти из-под той коряги, под которой вы провели последние лет десять, и поставить себе какую-нибудь современную IDE :)


                    1. BigDflz
                      03.12.2021 19:59
                      +1

                      когда нечего сказать - начинаются такие ответы.

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


                      1. FanatPHP
                        03.12.2021 20:12

                        Верно, не круче. Но вы-то утверждаете, что она хуже

                        Что, мягко говоря, не проходит проверку реальностью ;-)


                      1. BigDflz
                        03.12.2021 21:02

                        кто сказал хуже? посмотрите линейку продуктовDbForge - под каждую субд -своя ide. я пользуюсь для mysql и очень доволен.


                      1. FanatPHP
                        03.12.2021 23:08

                        Вы. Вы воображаете себе, будто "в ide основного кода нет подсветки кода sql". Но на предложение поставить себе современную IDE и убедиться, что это не так, начинаете по своему обыкновению юлить и вертеться как уж на сковородке.


              1. FanatPHP
                03.12.2021 18:32
                +1

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

                Я не буду здесь углубляться в тонкости синтаксиса процедур, а приведу куда более простой пример инъекции на базе кода из того топика

                $id = "0);DELETE FROM users; -- "
                $db->query('CALL get_order(' . $id . ')');

                "Ну что, сынку - помогли тебе твои ляхи?" :)


                1. BigDflz
                  03.12.2021 19:27

                  прежде чем такое советовать надо проверять

                  вот это 0);DELETE FROM users; --
                  в виде like "%0);DELETE FROM users; --%" и будет искаться в поле keywords


                  1. FanatPHP
                    03.12.2021 19:35

                    Совершенно верно! Проверять надо обязательно! Вот и проверьте :)

                    Введите строчку

                    get_order(0);DELETE FROM users; -- )

                    В своем клиенте базы данных, и посмотрите на результат. Только очень прошу - не на боевой базе!


                    1. BigDflz
                      03.12.2021 19:44

                      я то проверял, вот только надо быть полным идиотом, что такое в коде допустить.
                      я делаю так:
                      String f = String.format("{call %s(%s,'%s')}", "x13", s[0], s[1]);
                      CallableStatement proc = con.prepareCall(f);


                      1. FanatPHP
                        03.12.2021 20:18

                        О! Да вы не настолько безнадежны, каким хотите казаться :)

                        Но вам надо определиться: всё таки "вот это 0);DELETE FROM users; --<br>в виде like "%0);DELETE FROM users; --%" и будет искаться в поле keywords ", или "надо быть полным идиотом"

                        Потому что неувязочка получается :)


                      1. BigDflz
                        03.12.2021 20:24

                        если есть паранойя инъекций - то можно проверять всё что угодно перед вызовом. если у Вас такая строка
                        get_order(0);DELETE FROM users; -- ) считается реальной ситуацией то мне нечего на это сказать.
                        ЗЫ <br> заменяется Shift+Enter


                      1. FanatPHP
                        03.12.2021 20:44

                        Во-первых, хочу поблагодарить за доставленное удовольствие! Я прекрасно скоротал время перед концертом !

                        А во-вторых осмелюсь напомнить исходную фразу, с которой и началась эта увлекательная дискуссия:

                        используйте хранимые процедуры и это не будет поводом для боязни инъекций

                        На мой не искушённый взгляд он немного противоречит утверждению "если вас заедает паранойя, то надо проверять что-то ещё".

                        Или, выражаясь немного вульгарно, вы окончательно заврались :))


                      1. FanatPHP
                        03.12.2021 20:30

                        Ахаха, вы добавили код к своему комментарию! Он просто прекрасен! Вы просто семимильными шагами осваиваете реальную защиту от инъекций! Вы уже продвинулись на целый шаг!

                        Мы пока оставим в стороне неудобный вопрос, *а зачем нам вообще тогда нужна процедура*, и займёмся куда более насущным - а именно, вопросом *экранирования кавычек* в SQL запросах, который прекрасно освещен в известном комиксе про мальчика по имени Bobby Tables. Вам же, несомненно, он знаком? ;)


                      1. BigDflz
                        03.12.2021 20:56
                        -1

                        если Вас пугают кавычки - то они должны вас пугать и в использовании простых запросах.

                        я не параноик, поэтому я останавливаюсь на приемлемой стадии защиты.


                      1. FanatPHP
                        03.12.2021 23:11

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


                      1. BigDflz
                        04.12.2021 03:33
                        -1

                        Вы ответили на этот комент, но на комент с проверкой промолчали....

                        обозвали меня вруном, невежей, и прочее.
                        и, скорее всего, Вы минуснули в карму.
                        И это называется демократия по хабру.


                      1. BigDflz
                        04.12.2021 03:55

                        могли бы показать Ваши хвалимые подготовленные запросы.


                      1. BigDflz
                        04.12.2021 04:18

                        .те же хранимые процедуры могут иметь вид ваших подготовленных запросов
                        String SQL = "{call getlastname (?, ?)}";
                        CallableStatement cs = cn.prepareCall(SQL);
                        cs.setInt(1, 1658468);
                        cs.registerOutParameter(2,java.sql.Types.VARCHAR);
                        cs.execute();
                        String lastName = cs.getString(2);

                        о чем Вы скромно промолчали.


                      1. FanatPHP
                        04.12.2021 09:35
                        +1

                        Могут. И должны. И все нормальные разработчики именно так и поступают.

                        Но тогда возникает вопрос - а зачем нам тогда вообще каждый запрос заворачивать в процедуру? Если всё затевалось вроде бы для защиты от инъекций, но оказывается и сам вызов процедуры надо защищать? Именно защищать, без всех этих клоунских "нужно быть идиотом", "если у вас паранойя", "если пугают кавычки".

                        Но если запрос УЖЕ защищён, если подготовленным запросом можно безопасно выполнить как обычный запрос, так и вызвать процедуру, то зачем тогда нам тогда вообще процедуры? Да, сами по себе, иногда процедуры бывают нужны. Но каждый-то запрос тогда зачем? Если всё равно эта "защита" не работает, и при этом есть действительно надежный способ, который к тому же гораздо проще? Зачем заворачивать каждый запрос в отдельную процедуру? Зачем при изменении запроса не только обновлять код на сервере, но и накатывать миграцию в БД? Зачем писать лишний код и совершать лишние телодвижения?

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


                      1. BigDflz
                        04.12.2021 10:36
                        -1

                        я показал проверку - Вы в кусты.

                        зачем заворачивать? если Вам приятно разглядывать в коде строку на несколько экранов без подсветки - Ваше право. Если Вам нравится отлаживать запрос компилируя код - Ваше право.

                        я предпочитаю все это делать предназначенным для этого инструментом.

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

                        где увидели в процедурах лишний код?

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


                      1. BigDflz
                        03.12.2021 21:20

                        не поленился , проверил

                        вот такой код
                        String f = String.format("{call %s();DELETE FROM TABLE1 ;-- }", "page3_xxx010 ");
                        вот что получаю:
                        java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DELETE FROM TABLE1 ;-- ()' at line 1 at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120)


      1. FanatPHP
        01.12.2021 18:58

        Не надо повторять эту чушь, пожалуйста.


  1. slivorezka
    30.11.2021 18:20
    +1

    Ув. автор, вместо траты время на написания этой статьи в стиле ответа на Stack Overflow, лучше потратьте время на прочтения вот этой статьи PHP: Правильный Путь.
    А еще лучше, чтобы избежать многих простых ошибок, установите и настройте в своей IDE: Psalm, PHPStan или PhpCsFixer.

    Ничего личного, просто при виде этого кода мои глаза наполнились слезами, и это не слезы счастья, увы.


    1. LearnPC Автор
      30.11.2021 18:38

      Спасибо, обязательно прочту


    1. FanatPHP
      01.12.2021 20:45
      +1

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


  1. alex-khv
    30.11.2021 19:00

    Статья напоминает AI компилятор.

    Человек пишет код, а потом в комментариях ему пишут его ошибки.


    1. LearnPC Автор
      01.12.2021 19:18

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


  1. varanio
    01.12.2021 17:57

    Зачем это тут на Хабре? Ужасный код начинающего программиста


    1. LearnPC Автор
      01.12.2021 19:16

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


      1. FanatPHP
        01.12.2021 19:28

        Вам этих аргументов уже насовали полную панамку.

        С БД не умеете работать совсем.

        Героическая борьба с кодировками на пустом месте, какой-то ад с пересадками.

        Банальное неумение обратиться к массиву по индексу (это совсем уж какой-то детский сад)

        Если этого мало - классический винегрет из SQL и HTML, которые в нормальном коде вообще никогда не должны пересекаться. Неумение правильно обрабатывать ошибки.

        В итоге без ошибок вам удалось написать только одну команду - glob(). И вы всё ещё не уверены, что с вашим кодом не так? Всё ещё терзают сомнения?


      1. FanatPHP
        01.12.2021 20:51

        Но самое плохое, из-за чего я уже жалею, что исправил вам карму - ваши ответы похожи на реплики китайского болваничка: "учту", "прочитаю", "приму к сведению". Когда? Когда-нибудь потом? Вы не задали ни одного уточняющего вопроса. Не попытались разобраться ни в одной теме сразу же. Не сделали самостоятельно ни одного вывода из сделанных вам замечаний. Не открыли для себя ничего нового.

        Вот всё это - действительно очень плохо. Ошибки в коде - это ерунда. Но если не горят глаза, если нет интереса сразу же закатать рукава и разобраться, если все исправления сразу же откладываются в долгий ящик - вот тогда ошибки и останутся неисправленными.


        1. LearnPC Автор
          01.12.2021 21:24

          Я не готов задавать уточняющие вопросы до того, как адекватно прочту на что мне указали и с чем это едят.

          Мои глаза не потухли, даже с учётом количества какашек в моей "панамке", просто сейчас я пытаюсь уложить тонну материала в голове.

          И да, я прекрасно понимаю, что у меня получился "костыль", "говнокод" и мало что здесь можно найти хорошего. Действительно дали кучу полезных комментариев с указанием того, на что стоит обратить внимание, но при этом я не вижу смысла заходить с вердиктом "Говнокод!" без аргументов, если до тебя это уже сказали (это относится к пользователю varanio).

          А за карму отдельное спасибо, потому что это одна из причин, почему глаза не потухли после этой статьи


          1. LearnPC Автор
            01.12.2021 21:37

            Ко всему прочему, чтобы банально прочесть что-то по существу написанных комментариев, надо явно не 1 день (с учётом того, что у меня все таки есть основная работа). Поэтому обещаю, что скоро вернусь с вопросами, но более подготовленный