Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Задача была такова: Есть общая папка с изображениями, к каждому из которых проставлены ключевые слова (определяющие конкретного человека/организацию). Необходимо получать перечень изображений с определенным тегом для последующего вывода галереи на странице этого человека.
Просьба не судить очень строго, я лишь начинающий в php и mysql, но основная проблема и цель написания данной статьи в том, что в поисках решения своей проблемы я перерыл очень много форумов и сайтов, но адекватного ответа так и не нашел.
В связи с тем, что изображений достаточно много и работать с ними в последствии надо с помощью скриптов/запросов, было принято решение заносить имена файлов и ключевые слова в БД, а после этого SQL-запросами получать необходимые данные.
Был выработан следующий алгоритм действий:
Что происходит дальше пока не сильно важно, потому что основные проблемы родились именно на пункте 4.
Итак, разберемся что у нас происходит на каждом этапе:
Тут особо проблем не возникло, спасибо функции glob();
Здесь тоже проблем особо не возникает. Единственный момент, для того, чтобы метатеги выводились в адекватном виде, то было бы хорошо, чтобы они были написаны латиницей.
Пара циклов, проверка, как говорится тяп-ляп и готово.
Данная статья не про развертывание бд на сервере, поэтому сразу перейдём к делу.
Нам необходимо:
Здесь начинается самое интересное, потому что именно отсюда мы плавно втекаем в ошибки, которые устраняются не совсем очевидным образом
Привожу сразу пример моей функции с комментариями
А перед тем, как мы перейдём к следующему пункту, я скажу, что если собрать этот код воедино, то мы получим проблему (возникает не у всех, если сервер настроен правильно со всех сторон, но это не точно), которая решается не совсем тривиально (как мне кажется)
У меня получилась следующая ситуация:
Полученная строка ключевых слов к изображениям в формате 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-скрипта, но это в будущем, потому что статья ориентировалась на другой аспект.
И, как говорится:
Просьба для тех, кто будет читать, особенно с целью критиковать
Основная задумка статьи, помочь новичкам типа меня настроить один из частных случаев взаимодействия exif-php-mysql-php-html.
Я за объективную критику, если у вас есть рациональные предложения по улучшению кода, я с радостью их послушаю и приму к сведению.
Еще раз напомню, что это чуть ли не первое моё написание php-скрипта, где необходимо работать с БД на сервере, поэтому у меня действительно по-тупому всё сделано в одном скрипте.
Просьба не судить очень строго, я лишь начинающий в php и mysql, но основная проблема и цель написания данной статьи в том, что в поисках решения своей проблемы я перерыл очень много форумов и сайтов, но адекватного ответа так и не нашел.
В связи с тем, что изображений достаточно много и работать с ними в последствии надо с помощью скриптов/запросов, было принято решение заносить имена файлов и ключевые слова в БД, а после этого SQL-запросами получать необходимые данные.
Был выработан следующий алгоритм действий:
- Получить список файлов (*.jpeg);
- Циклом пройти по всем файлам и вытащить exif данные (а именно тег keywords);
- Занести имена файлов и ключевые слова в базу данных;
- Получение списка файлов по определенному тегу;
Самое приятное— исправление ошибок.
Что происходит дальше пока не сильно важно, потому что основные проблемы родились именно на пункте 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-скрипта, но это в будущем, потому что статья ориентировалась на другой аспект.
И, как говорится:
Спасибо за внимание! Я готов выслушать ваши вопросы!