11
Использование PHP для управления информацией в базах данных MySQL
В этой главе показано, как управлять записями в базах данных MySQL из PHP+ сценариев. В частности, здесь рассматривается вставка новых записей в таблицу базы данных с помощью PHP, а также удаление и обновление записей, уже имеющихся в таблице базы данных.
Чтобы дать читателю возможность разобраться в этой функциональности, авторы подробно рассматривают разработку двух сценариев: регистрации пользователей (User Manager) и протоколирования посещения страниц (Access Logger). Кроме того, в этой главе показано, как усовершенствовать сценарий для просмотра пользователь+ ских записей, созданного в главе 10, добавив в него возможность управления запися+ ми пользователей. Эта глава завершает серию глав, посвященных MySQL и взаимо+ действию PHP и MySQL.
Вставка записей с помощью PHP
В предыдущей главе было показано, как вставлять данные в таблицу, используя клиентскую программу mysql. Например, следующий запрос вставляет запись для пользователя Pads:
mysql> INSERT INTO user VALUES( -> NULL,
Использование PHP для управления информацией в базах данных MySQL 443
-> 'Pads',
-> Password(12345), -> 'Brian Reid', -> 'Winger',
-> 'Stickypads@doggies_rugby.com',
-> 'Высококлассный перехватчик мячей.');
Той же цели можно достичь с помощью PHP:
$result = mysql_query("INSERT INTO user VALUES( ... )");
Обычно для вставки значений в таблицу вместо непосредственного указания зна+ чений в запросе используются PHP+переменные:
$query = "INSERT INTO user VALUES(NULL, '$userid', password('$userpassword'),
'$username', '$userposition', '$useremail', '$userprofile')";
$result = mysql_query($query);
Напомним, что поле usernumber имеет тип AUTO_INCREMENT ++++++ запрос передает в это поле NULL, чтобы его значение в каждой новой добавляемой записи было на единицу больше, чем у предыдущей записи.
Иногда возможность получать результирующее значение бывает очень удобной; например, можно сделать так, чтобы сценарий при регистрации нового пользователя показывал ему его регистрационный номер. Автоматически увеличенное число, сге+ нерированное последним INSERT+запросом, можно получить с помощью функции mysql_insert_id(). Например:
$link_id = db_connect('sample_db');
$result = mysql_query("INSERT INTO user (userid) VALUES('sphinx')");
$usernumber = mysql_insert_id($link_id); echo "Спасибо. Ваш номер - $usernumber.";
Специальные символы
Как уже отмечалось в главе 9, прежде чем вставлять строковое значение в таблицу базы данных, необходимо экранировать каждую одинарную кавычку в этом значении, в противном случае возникает ошибка. В предыдущей главе приводился пример с вставкой строки ‘‘I’m a rugby player’’ в поле userprofile типа TEXT. Ввиду того, что эта строка ограничена двойными кавычками, она хорошо вставляется в базу данных как с помощью клиентской программы mysql, так и с помощью PHP. Чтобы заключить эту строку в одинарные кавычки, понадобилось бы экранировать одинарную кавычку, содержащуюся в строке. Рассмотрим несколько примеров:
"I'm a PHP developer." Работает.
'I\m a PHP developer.' Тоже работает. 'I'm a PHP developer.' Не работает.
Однако почему приходится учитывать эти различия? Несомненно, лучше всего экра+ нировать все вхождения кавычек, как двойных, так и одинарных ++++++ в конце концов, в PHP экранированные кавычки работают как в строковых значениях, ограниченных одинар+ ными кавычками, так и в строковых значениях, ограниченных двойными кавычками. Все зависит от программы. Если поставить обратную косую черту перед одинарной ка+ вычкой в первом примере, то получится следующая строка: "I\'m a PHP developer".
Если впоследствии вывести эту строку с помощью PHP, то она отобразится в брау+ зере в таком же виде, т.е. с обратной косой чертой, а это очевидно совсем не то, что нужно. Данная проблема решается в PHP с помощью пары очень удобных функций: addslashes()и stripslashes().
Эти функции тесно связаны друг с другом. Функция addslashes() принимает строковый аргумент, предваряет каждый символ, который должен быть экранирован в запросах к базе данных, символом обратной косой черты, а затем возвращает моди+ фицированную строку. Функция stripslashes() решает обратную задачу, удаляя из строки все символы обратной косой черты.
Следующие строки кода показывают, как можно использовать эти функции для вставки записей в таблицу, экранируя значения так, как это требуется:
$link_id = db_connect('sample_db'); $userprofile = addslashes($userprofile);
$query = "INSERT INTO user (userprofile) VALUES('$userprofile')"; mysql_query($query, $link_id);
$userprofile = stripslashes($userprofile);
Если предположить, что в переменной $userprofile хранится строка:
I'm a PHP developer.
то после обработки функцией addslashes() эта переменная получит значение:
I\'m a PHP developer.
Теперь эту переменную можно безопасно передавать в SQL+оператор. Когда понадо+ бится прочитать это значение из базы данных, сервер вернет строку с кавычкой, но без escape+последовательностей. Однако если использовать переменную $userprofile в последующем коде, то посторонние символы обратной косой черты будут искажать правильно отформатированный текст. Чтобы привести эту строку в порядок, следует использовать функцию stripslashes().
Функция htmlspecialchars()
Вопросы, связанные со специальными символами, не заканчиваются экраниро+ ванием кавычек. Как известно, определенные символы имеют в HTML специальное значение, например, символ < обозначает начало HTML+тега. Если этот символ со+ держится во введенной пользователем строке, которая затем выводится на HTML+ странице, то это неизбежно приведет к неверному отображению Web+страницы в браузере. Синтаксический анализатор HTML+кода будет связывать все, что следу+ ет после этого символа (вплоть до символа >), с каким+либо тегом, хотя вряд ли это будет действительно так.
Чтобы избежать этого, можно представить указанный символ в форме HTML+ последовательности, которая выглядит так:
<
PHP+функция htmlspecialchars(), которая уже рассматривалась вкратце в главе 5, решает задачу преобразования специальных HTML+символов в их текстовую форму, что значительно упрощает работу программиста. Так же как функции, добавляю+ щие/удаляющие обратную косую черту, которые рассматривались выше, htmlspecialchars() принимает строковый аргумент, заменяет все специальные символы соответствующими безопасными формами и возвращает модифицированную строку:
$userprofile = htmlspecialchars($userprofile);
Использование PHP для управления информацией в базах данных MySQL 445
Преобразовываются следующие символы:
Специальный символ |
HTML-последовательность |
|
|
& (амперсанд) |
& |
" (двойная кавычка) |
" |
< (знак меньше) |
< |
> (знак больше) |
> |
|
|
Теперь, после того как были раскрыты некоторые тонкости, связанные со встав+ кой информации в базы данных с помощью PHP, следует рассмотреть обновление и удаление существующих данных.
Обновление и удаление записей в таблицах
Предположим, что один из зарегистрированных пользователей сайта намерен из+ менить его регистрационное имя, так как в нем обнаружилась опечатка. Вместо того чтобы заменять всю запись для данного пользователя, можно изменить только значе+ ние в поле username имеющейся записи. Очевидно, что для этого используется ко+ манда UPDATE. Рассмотрим пример соответствующего оператора:
UPDATE user SET username = 'Darren Ebbs'
WHERE username = 'Daren Ebbs'
Указанная команда изменяет в поле username все вхождения строки Daren Ebs
на Darren Ebbs.
Чтобы удалить из таблицы существующую запись, необходимо использовать ко+ манду DELETE:
DELETE FROM access_log WHERE page = 'who.html'
Она удаляет все записи, в которых значение поля page равно 'who.html'.
Ниже приводится пример такого UPDATE+запроса, который использовать не следу* ет. Он обновляет все записи в таблице user так, что любой пользователь впоследст+ вии сможет входить на сайт, используя пароль comeonin:
UPDATE user SET userpassword = password('comeonin')
Еще хуже последствия запроса, который очищает таблицу user:
DELETE FROM user
Такого рода грубые ошибки могут допускать даже очень опытные администраторы баз данных ++++++ это последствия невнимательности во время ввода запроса. Используя данные команды, следует быть очень внимательным.
Предположим, что из таблицы access_log требуется удалить все записи, начиная с 1999 года, так как они больше не нужны. Для этого можно использовать такой запрос:
mysql> DELETE FROM access_log WHERE accessdate LIKE '1999%';
Пользователь может захотеть время от времени изменять свой пароль. Следующий запрос изменяет пароль пользователя на guessit:
mysql> UPDATE user SET userpassword = password('guessit') -> WHERE userid = 'Greeny';
Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
Сервер сообщает, что с заданным условием совпала одна строка, которая, следова+ тельно, и была изменена. Если сервер не находит совпадающих записей, то обновле+ ние не выполняется:
mysql> UPDATE user SET userpassword = password('guessit') -> WHERE userid = 'Ginger';
Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0
Можно использовать существующие значения для обновления записи. Например,
вполе visitcount содержится количество посещений определенным пользователем какой+либо Web+страницы. Если пользователь посещает данную страницу снова, то значение в поле visitcount необходимо увеличить на 1. Для этого можно получить текущее значение из таблицы access_log, прибавить к нему 1, а затем записать
втаблицу новое значение. Однако той же цели можно достичь намного проще ++++++ ис+ пользуя существующее значение в UPDATE+запросе. Следующий запрос делает как раз то, что нужно ++++++ он увеличивает значение поля visitcount для пользователя Dodge, посещающего страницу /score.html:
mysql> UPDATE access_log SET visitcount = visitcount + 1 -> WHERE user_id = 'Dodge' AND page = '/score.html';
Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
Поле lastaccesstime необходимо обновлять отдельно (этот вопрос обсужда+ ется далее).
Строковые значения обновляются аналогично:
mysql> UPDATE user SET userid = concat(userid, '_1'); Query OK, 6 row affected (0.00 sec)
Rows matched: 6 Changed: 6 Warnings: 0
В запросе используется функция MySQL+сервера для слияния строк, которая добав+ ляет строку _1 к каждому значению поля userid. Например, в данном случае иденти+ фикатор пользователя Dodge станет равным Dodge_1. Этот метод оказывается очень удобным, когда новый пользователь запрашивает идентификатор, который уже исполь+ зуется другим пользователем, и нужно предложить альтернативный идентификатор.
Если бы использовался запрос SET userid = userid + ‘_1’, то значение поля userid было бы равным 0, а не Dodge_1.
Допускаются также множественные обновления одного поля:
mysql> UPDATE access_log
-> SET visitcount = visitcount + 1, visitcount = visitcount * 2; Query OK, 6 rows affected (0.00 sec)
Rows matched: 6 Changed: 6 Warnings: 0
Присвоения значений выполняются слева направо, поэтому во втором присвое+ нии используется уже увеличенное на 1 значение поля visitcount. Другими слова+ ми, сначала ко всем текущим значениям поля visitcount прибавляется 1, а затем по+ лученные числа удваиваются.
К настоящему моменту читатели уже, вероятно, привыкли видеть отчеты MySQL+ сервера в командной строке. Когда используется запрос DELETE или UPDATE, сервер автоматически сообщает количество задействованных строк, время, потраченное на выполнение данного запроса, количество совпадающих и измененных строк, а также выводит любые сгенерированные предупреждения.
Использование PHP для управления информацией в базах данных MySQL 447
Первый из этих параметров можно получить в PHP+сценарии, используя функцию mysql_affected_rows(). Она в качестве аргумента принимает идентификатор со+ единения с базой данных и возвращает количество записей, затронутых предыдущим SQL+запросом ++++++ DELETE+запросом в следующем примере:
$link_id = db_connect("sample_db"); mysql_query("DELETE FROM access_log
WHERE page = '/penalties.html'");
$num_rows = mysql_affected_rows($link_id); echo "Было удалено $num_rows пользователей.";
Если страницу нарушений посещал только один пользователь, то сценарий вывел бы следующее сообщение:
Было удалено 1 пользователей.
Если сценарий удаляет все записи в таблице, то функция mysql_affected_rows() возвращает 0.
Работа с полями даты и времени
Пожалуй, из всех типов данных в MySQL самыми сложными для обработки явля+ ются дата и время. Для правильной работы с ними необходима длительная практика. В полях этих типов значения даты и времени хранятся в следующих форматах:
Тип данных |
Формат |
|
|
DATE |
2004-01-01 |
TIME |
12:00:00 |
DATETIME |
2004-01-01 12:00:00 |
TIMESTAMP |
20000101120000 |
YEAR |
2004 |
|
|
Если попытаться вставить в такое поле неверно отформатированное значение да+ ты и/или времени, то это поле в соответствующей записи будет заполнено нулями. Например, вставка строки Pads в поле TIME+типа приведет к тому, что в данном поле сохранится значение 00000000000000:
mysql> select accessdate from access_log where userid = 'Pads' AND page ='';
+---------------- |
|
+ |
| accessdate |
| |
+---------------- |
|
+ |
+ 00000000000000 | |
+---------------- |
|
+ |
1 |
row in set (0.00 sec) |
MySQL+сервер предоставляет несколько встроенных функций, связанных с да+ той и временем, которые позволяют гарантировать вставку корректных значений. Выше уже упоминалась функция now(), которая возвращает текущую дату и время в формате DATETIME:
mysql> select now(); |
|
+-------------------------- |
|
+ |
| now() |
| |
+-------------------------- |
|
+ |
| 2004-03-04 19:30:15 |
| |
+-------------------------- |
|
+ |
1 |
row in set (0.00 sec) |
|
а также функции curdate()и curtime(), которые возвращают по отдельности зна+ чения DATE и TIME:
mysql> select curdate(), curtime();
+------------ |
|
+----------- |
+ |
| curdate() |
| curtime() | |
+------------ |
|
+----------- |
+ |
| 2004-03-04 |
| 19:31:33 |
| |
+------------ |
|
+----------- |
+ |
1 |
row in set |
(0.00 sec) |
|
Для вставки значения в поле типа TIMESTAMP можно указать либо дату и время
ввиде строки из 14 цифр, либо NULL, в результате чего в поле сохраняется текущая системная дата и время в таком же формате. Чтобы правильно обновлять записи
втаблице access_log, следует использовать команду:
mysql> UPDATE access_log
-> SET visitcount = visitcount + 1, accessdate = NULL -> WHERE userid = 'Dodge' AND page = '/score.html';
Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
Серверная функция date_format() возвращает отформатированное значение даты и времени. Она принимает аргумент DATE+ или DATETIME+типа, а также строку формата. Если поле lastaccesstime имеет тип DATE или DATETIME, то можно ис+ пользовать следующую команду:
mysql> SELECT date_format('2004-03-04 22:23:00', '%M %e, %Y'); +-------------------------------------------------+
| date_format('2004-03-04 22:23:00', '%M %e, %Y') |
+------------------------------------------------- |
|
+ |
| March 4, 2004 |
| |
+------------------------------------------------- |
|
+ |
1 |
row in set (0.03 sec) |
|
Существует более 30 спецификаторов, которые можно использовать в строке формата. Ниже перечислены наиболее распространенные из них.
Спецификатор |
Описание |
|
|
%s |
Секунда в двузначной числовой форме (00,01...) |
%i |
Минута в двузначной числовой форме (00,01...) |
%H |
Час в двузначной 24-часовой числовой форме (00,01...) |
%h |
Час в двузначной 12-часовой числовой форме (00,01...) |
%T |
Время в 24-часовой форме (hh:mm:ss) |
%r |
Время в 12-часовой форме (hh:mm:ss AM|PM) |
%W |
День недели (Monday, Tuesday, Wednesday...) |
%a |
Сокращенное называние дня недели (Mon, Tue, Wed...) |
%d |
День месяца в двузначной числовой форме (01,02,03...) |
%e |
День месяца в числовой форме (1,2,3...) |
%D |
День месяца с порядковым суффиксом (1st, 2nd, 3rd...) |
%M |
Месяц (January, February...) |
%b |
Сокращенное название месяца (Jan, Feb...) |
%Y |
Год в четырехзначной числовой форме |
%y |
Год в двухзначной числовой форме |
|
|
Использование PHP для управления информацией в базах данных MySQL 449
Полный перечень спецификаторов приведен в документации по MySQL www.mysql.com/doc/en/Date_and_time_functions.html#IDX1335.
В коде функции view_record() сценария для просмотра пользовательских запи+ сей (см. предыдущую главу) значение поля accessdate форматировалось с помощью следующих строк кода:
$accessdate = substr($query_data["accessdate"], 0, 4) . '-' . substr($query_data["accessdate"], 4, 2) . '-' . substr($query_data["accessdate"], 6, 2) . ' ' . substr($query_data["accessdate"], 8, 2) . ':' . substr($query_data["accessdate"], 10, 2) . ':' . substr($query_data["accessdate"], 12, 2);
которые генерировали значение даты и времени в следующем формате:
2004-01-22 10:31:35
Той же цели можно достичь с помощью одного запроса:
mysql> SELECT date_format(accessdate, '%Y-%m-%d %H:%i:%s')
|
-> FROM access_log WHERE userid='Dodge'; |
+---------------------------------------------- |
+ |
| date_format(accessdate, '%Y-%m-%d %H:%i:%s') |
+---------------------------------------------- |
|
|
+ |
| 2004-03-11 |
12:22:49 |
| |
| 2004-01-25 |
16:41:33 |
| |
+---------------------------------------------- |
|
|
+ |
2 |
rows in set |
(0.00 sec) |
|
Если необходимо узнать день недели, когда состоялось последнее посещение страницы определенным пользователем, то можно применить следующий запрос, в котором используется серверная функция dayname(), возвращающая название дня недели для заданной даты:
mysql> SELECT accessdate, dayname(accessdate) FROM access_log -> WHERE userid='Dodge';
+---------------- |
|
+--------------------- |
+ |
| accessdate |
| dayname(accessdate) | |
+-------------------------------------- |
|
|
+ |
| 20040311122249 |
|Thursday |
| |
| 20040125164133 |
|Sunday |
| |
+---------------- |
|
+--------------------- |
+ |
2 |
rows in set (0.01 sec) |
|
Серверная функция to_days() вычисляет общее количество дней с нулевого года нашей эры:
mysql> SELECT to_days(accessdate) FROM access_log
|
-> WHERE userid='Dodge'; |
+--------------------- |
|
+ |
| to_days(accessdate) | |
+--------------------- |
|
+ |
| |
732016 |
| |
| |
731970 |
| |
+--------------------- |
|
+ |
2 |
rows in set (0.00 sec) |
Предположим, что требуется узнать количество дней, прошедших после последне+ го посещения пользователем сайта:
mysql> SELECT userid, to_days(now()) - |
to_days(accessdate) |
|
-> FROM access_log GROUP BY userid |
LIMIT 5; |
+-------- |
+-------------------------------------- |
+ |
| userid | to_days(now()) - to_days(accessdate) | |
+-------- |
+-------------------------------------- |
+ |
| Brian |
| |
7 |
| |
| Dodge |
| |
0 |
| |
| Greeny | |
47 |
| |
| Mac |
| |
49 |
| |
| Nicrot | |
47 |
| |
+-------- |
+-------------------------------------- |
|
+ |
5 rows in set (0.01 sec) |
|
|
Данный запрос в частности показывает, что пользователь Mac не посещал сайт 49 дней. Если это обстоятельство огорчает владельца сайта, то можно просто удалять пользо+ вателей, которые не посещали сайт более чем 48 дней. Это можно сделать с помощью следующего запроса:
mysql> DELETE FROM access_log WHERE to_days(now()) - to_days(accessdate) > 48; Query OK, 1 row affected (0.01 sec)
Выше приведены лишь простые примеры некоторых серверных MySQL+функций для обработки даты и времени. За более подробной информацией рекомендуется об+ ратиться к справочному руководству по MySQL на Web+сайте www.mysql.com.
Получение информации о таблицах в базе данных
Выяснение информации об используемой базе данных часто бывает настолько же важным, насколько важны сами данные, содержащиеся в базе. Например, нередко для принятия решения о следующей операции над таблицей необходимо узнать некото+ рые сведения об этой таблице. Сделать это несложно ++++++ получить информацию о таб+ лицах баз данных с помощью PHP так же просто, как получить сами данные из таблиц.
Для просмотра структуры таблиц базы данных предназначены следующие функции.
mysql_list_fields(): эту функцию следует вызывать первой для получения информации о полях таблицы. Функция принимает три аргумента (имя базы данных, имя таблицы и необязательный идентификатор подключения к базе данных) и возвращает указатель результата, который ссылается на список всех полей в заданной таблице заданной базы данных. Например, чтобы получить указатель на список всех полей в таблице user базы данных sample_db, можно использовать следующий код:
$result = mysql_list_fields("sample_db", "user");
Переменная $result теперь содержит указатель на список полей в таблице user. Пример использования данной функции будет представлен позднее.
mysql_num_fields(): принимает в качестве аргумента указатель на результат (возвращаемый предыдущей функцией) и возвращает общее количество полей результирующего множества, на которое ссылается данный указатель. Чтобы получить количество полей в таблице user, достаточно передать в функцию mysql_num_fields() указатель $result, полученный от функции mysql_list_fields():
$number_of_fields = mysql_num_fields($result);
Переменная $number_of_fields содержит (как и ожидалось) количество полей таблицы user.
msyql_field_name(), mysql_field_len() и mysql_field_type(): эти функции возвращают имя поля, длину поля и тип поля соответственно. Каждая
Использование PHP для управления информацией в базах данных MySQL 451
из функций принимает в качестве аргументов указатель на результирующее множество и индекс поля, определяющие таблицу и поле (отсчет полей начи+ нается с нуля) соответственно. Например, чтобы получить длину поля username в таблице user, сначала необходимо знать, что поле username ++++++ четвер+ тое поле таблицы и, следовательно, имеет индекс 3 (индексы начинаются с нуля), а затем можно использовать следующий код:
$username_length = mysql_field_len($result, 3);
Так как при определении поля username его длина была задана равной 30 симво+ лам, в результате выполнения данной функции переменная $username_length должна содержать число 30. Две другие функции работают почти так же.
mysql_field_flags(): принимает указатель на результирующее множество и индекс поля, а возвращает строку, содержащую атрибуты для заданного поля. Атрибутами поля могут быть NULL или NOT NULL, PRIMARY KEY, UNIQUE. Чтобы записать атрибуты поля username (таблицы user) в переменную $attributes, можно использовать следующий код:
$attributes = mysql_field_flags($result, 3);
Поле username имеет единственный атрибут ++++++ NOT NULL, поэтому переменная $attributes получит это значение.
Практика Получение информации о полях
Рассмотрим упомянутые выше функции в действии. Следующий сценарий, metadata.php, возвращает имя, длину и тип каждого поля, определенного в таб+ лице user:
<?php
include "./common_db.inc"; $link_id = db_connect();
$result = mysql_list_fields("sample_db", "user", $link_id);
for($i=0; $i < mysql_num_fields($result); $i++ ) { echo mysql_field_name($result,$i );
echo "(" . mysql_field_len($result, $i) . ")"; echo " - " . mysql_field_type($result, $i) . "<BR>";
}
?>
Результат работы данного сценария представлен на рис. 11.1.
Как это работает
Как обычно, сначала сценарий подключает файл с функциями для работы с базами данных (common_db.inc), а затем подключается к серверу, используя функцию db_connect():
include_once "./common_db.inc"; $link_id = db_connect();
После этого вызывается функция mysql_list_fields() с указанием базы дан+ ных, таблицы и идентификатора соединения с используемой базой данных (в рас+ сматриваемом случае sample_db, user и $link_id соответственно):
$result = mysql_list_fields("sample_db", "user", $link_id);