webew
Войти » Регистрация
 
PHP
MySQL/MariaDB

Удобные функции PHP для работы с MySQL

10 октября 2010, 17:53

В статье предлагается несколько простых функций языка PHP для взаимодействия с СУБД MySQL, которые могут значительно облегчить повседневную работу программистов. Код функций можно скачать одним php-файлом.

Работа с MySQL силами встроенных функций PHP (таких как mysql_query(), mysql_fetch_assoc() и др.) не является чем-то сложным. Однако синтаксические конструкции, требуемые для обработки результатов запроса, обычно занимают пару лишних строк кода. Приходится либо повторять такой код каждый раз, либо использовать специальные библиотеки, которые призваны эту задачу автоматизировать. Эти библиотеки, однако, часто берут на себя дополнительные (обычно довольно бесполезные) задачи, в результате чего интерфейс их усложняется и такими библиотеками оказывается неудобно пользоваться.

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

mysql_q() — выполнение запроса и контроль ошибок

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

Функция mysql_q() помогает быстро найти проблемное место — в случае ошибки в запросе она выводит на экран сообщение с информацией об ошибке и прекращает работу скрипта2.

Рассмотрим пример1:

<?php

$query = "SELECT NOW"; // забыли поставить скобки после NOW()

mysql_query($query); // ничего не происходит

mysql_q($query); // а вот mysql_q() проинформирует об ошибке

?>

После работы mysql_q() на экране появится сообщение:

MySQL error in file /usr/home/www/наш_сайт/test.php at line 7 (function mysql_q):
Unknown column 'NOW' in 'field list'

SELECT NOW

Функция mysql_q() пригодна для любых запросов (не только SELECT):

mysql_q("INSERT VALUES ('что-то') INTO some_table");
mysql_q("SET @a = 1");
mysql_q("USE some_other_database");
mysql_q("TRUNCATE some_table");
mysql_q("DROP some_table");
mysql_q("START TRANSACTION");
// и др.

Подстановка параметров в запрос

Есть возможность подстановки в запрос элементов ассоциативного массива. Для этого запрос нужно составить с использованием специальных меток и в качестве второго аргумента передать функции mysql_q() массив замены:

$sql = "
    INSERT INTO products
    SET
      id = :id,
      price = :price,
      name = :name
    "
;
   
$params = array(
        'id' => 12,
        'price' => 10000,
        'name' => 'Телевизор',
    );

mysql_q($sql, $params);

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

    "
    INSERT INTO products
    SET
      id = 12,
      price = 10000,
      name = 'Холодильник'
    "

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

$sql = "
    SELECT * FROM products
    WHERE id IN (:ids)
       OR brand = :brand
    "
;

$params = array(
        'brand' => 'Полёт',
        'ids' => array(
                5,
                115,
                220,
                'сюда случайно попала строка',
                NULL
            ),
    );

mysql_q($sql, $params);

В итоге даст запрос

    "
    SELECT * FROM products
    WHERE id IN (5,115,220,'сюда случайно попала строка',NULL)
       OR brand = 'Полёт'
    "

Подстановка параметров в запрос поддерживается всеми функциями библиотеки (см. ниже).

Получить текст запроса после подстановки можно с помощью функции
mysql_substitute($sql, $params); (обычно это бывает полезно при отладке).

 

Далее речь пойдет о функциях для обработки запросов, которые возвращают результат — это, прежде всего, запросы SELECT, а также некоторые другие. Для отправки запросов все эти функции используют mysql_q().

В примерах будет использована вот такая таблица:

mysql> SELECT * FROM products;
+----+-----------+-------+---------------------+
| id | name      | price | created             |
+----+-----------+-------+---------------------+
|  1 | Веник     |   100 | 2010-10-09 22:24:14 |
|  2 | Швабра    |   500 | 2010-10-09 22:24:26 |
|  3 | Чайник    |  1500 | 2010-10-09 22:24:37 |
|  4 | Совок     |   150 | 2010-10-09 22:24:51 |
|  5 | Телевизор |  5000 | 2010-10-09 22:24:59 |
|  6 | Ведро     |   150 | 2010-10-09 22:25:18 |
+----+-----------+-------+---------------------+
6 rows in set (0.00 sec)

mysql_getcell() — получение одной ячейки

Функция mysql_getcell() предназначена для получения результатов запросов, которые состоят из одной строки и одного столбца (такие запросы называются скалярными). Результаты mysql_getcell() возвращает в виде скалярной переменной:

echo mysql_getcell("SELECT 1"); // 1
echo mysql_getcell("SELECT NOW()"); // 2010-10-09 22:30:05
echo mysql_getcell("SELECT COUNT(*) FROM products"); // 6
echo mysql_getcell("SELECT name FROM some_table ORDER BY price LIMIT 1"); // Телевизор
// и др.

Если в запросе встретится ошибка — в сообщении будет указано место, откуда вызывается mysql_getcell()3:

<?php
echo mysql_getcell("SELECT NOW");
?>

MySQL error in file /usr/home/www/наш_сайт/html/test.php at line 2 (function mysql_getcell):
Unknown column 'NOW' in 'field list'

SELECT NOW

Параметры для подстановки в запрос можно передавать вторым аргументом.

mysql_getrow() — получение одной строки

Функция mysql_getrow() служит для получения результатов запросов, которые состоят из одной строки. mysql_getrow() вернёт результат запроса в виде одномерного ассоциативного массива, ключами которого являются имена колонок, а значениями - то, что хранится в соответствующих ячейках:

<?php
$query = "SELECT * FROM products WHERE id = 5";
$data = mysql_getrow($query);
print_r($data);
?>
Array
(
    [id] => 5
    [name] => Телевизор
    [price] => 5000
    [created] => 2010-10-09 22:24:59
)

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

mysql_getcolumn() — получение одного столбца

Функция mysql_getcolumn() служит для получения результатов запросов, которые состоят из нескольких строк и одного столбца. mysql_getcolumn() возвращает результат запроса в виде одномерного массива:

<?php
$query = "SELECT name FROM products ORDER BY price DESC";
$data = mysql_getcolumn($query);
print_r($data);
?>
Array
(
    [0] => Телевизор
    [1] => Чайник
    [2] => Швабра
    [3] => Совок
    [4] => Ведро
    [5] => Веник
)

Можно и запросы типа таких:

<?php
print_r(mysql_getcolumn("SHOW TABLES LIKE 'products'"));
?>
Array
(
    [0] => products
)

Иногда бывает нужно иметь такой массив, ключи которого содержат значения какого-нибудь уникального поля (столбца) соответствующих записей. С помощью mysql_getcolumn() сделать это очень легко — нужно добавить это поле в запрос и указать в качестве второго аргумента TRUE. Ключами становятся значения того поля, которое указано в запросе первым:

<?php
$query = "SELECT id, name FROM products ORDER BY price DESC";
$data = mysql_getcolumn($query, TRUE);
print_r($data);
?>
Array
(
    [5] => Телевизор
    [3] => Чайник
    [2] => Швабра
    [4] => Совок
    [6] => Ведро
    [1] => Веник
)

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

mysql_gettable() — для табличных запросов

Табличным называется запрос, результат которого содержит более одной строки и более одного столбца. Функция mysql_gettable() возвращает его в виде двумерного массива:

<?php
$query = "SELECT * FROM products ORDER BY price DESC LIMIT 3";
$data = mysql_gettable($query);
print_r($data);
?>
Array
(
    [0] => Array
        (
            [id] => 5
            [name] => Телевизор
            [price] => 5000
            [created] => 2010-10-09 22:24:59
        )

    [1] => Array
        (
            [id] => 3
            [name] => Чайник
            [price] => 1500
            [created] => 2010-10-09 22:24:37
        )

    [2] => Array
        (
            [id] => 2
            [name] => Швабра
            [price] => 500
            [created] => 2010-10-09 22:24:26
        )
)
<?php
print_r(mysql_gettable("SHOW VARIABLES LIKE 'query_cache%'"));
?>
Array
(
    [0] => Array
        (
            [Variable_name] => query_cache_limit
            [Value] => 1048576
        )

    [1] => Array
        (
            [Variable_name] => query_cache_min_res_unit
            [Value] => 4096
        )

    [2] => Array
        (
            [Variable_name] => query_cache_size
            [Value] => 134217728
        )

    [3] => Array
        (
            [Variable_name] => query_cache_type
            [Value] => ON
        )

    [4] => Array
        (
            [Variable_name] => query_cache_wlock_invalidate
            [Value] => OFF
        )
)

Есть возможность записать в ключи массива значения уникального поля. Для этого поле нужно добавить в запрос (если оно уже не присутствует в неявном виде как часть *) и указать имя поля вторым аргументом4:

<?php
$query = "SELECT * FROM products ORDER BY price DESC LIMIT 3";
// вместо * можно было написать id, name, price, created
$data = mysql_gettable($query, 'id');
print_r($data);
?>
Array
(
    [5] => Array
        (
            [id] => 5
            [name] => Телевизор
            [price] => 5000
            [created] => 2010-10-09 22:24:59
        )

    [3] => Array
        (
            [id] => 3
            [name] => Чайник
            [price] => 1500
            [created] => 2010-10-09 22:24:37
        )

    [2] => Array
        (
            [id] => 2
            [name] => Швабра
            [price] => 500
            [created] => 2010-10-09 22:24:26
        )

)

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

mysql_write_row()

Одна из самых распространенных задач — это вставка в таблицу новой записи или обновление уже существующей по уникальному ключу с предварительным экранированием передаваемых данных. Эту задачу позволяет решать функция mysql_write_row().

Простой INSERT

Для вставки записи в таблицу качестве аргументов функции требуются имя таблицы и ассоциативный массив данных для вставки, в котором ключи соответствуют именам колонок. Функция вернёт id вставленной записи (см. mysql_insert_id).

<?php
// Допустим, есть пустая таблица вида
// CREATE TABLE goods (
//  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
//    name VARCHAR(255),
//    price DECIMAL(5),
//    description TEXT
// );

// Хотим вставить вот такую строку:

$data = [
    "name" => "Чайник",
    "price" => 3000,
    "description" => "Хороший и дорогой чайник. ' - кавычку экранировать не требуется"
];
   
mysql_write_row('goods', $data); // вернет 1 - id новой записи

Простой UPDATE

mysql_write_row() можно использовать и для обновления записей таблицы. Для этого ей нужно в качестве третьего аргумента передать значение уникального ключа. Результат работы функции в этом случае — количество изменившихся записей в таблице (см. mysql_affected_rows):

$data = [
    "description" => "Самый лучший чайник",
    "price" => 3500
];

// Следующие две записи идентичны:
mysql_write_row('goods', $data, array('id' => 3) );  // вернет 1, т.к. изменилась одна строка
mysql_write_row('goods', $data, 3); // скалярная величина считается значением столбца с именем 'id'

Обновлять можно также по условию на несколько колонок — в этом случае массив с ключами будет содержать несколько элементов. При этом одна и та же колонка может фигурировать и в части SET, и в части WHERE: код

$set_keys = array(
    'f1'=>11,
    'f2'=>22
);
$where_keys = array(
    'f2'=>2,
    'f3'=>3,
    'f4'=>NULL,
);
mysql_write_row('tablename', $set_keys, $where_keys);

даст запрос

UPDATE tablename SET f1=11, f2=22 WHERE f2=2 AND f3=3 AND f4 IS NULL

INSERT ... ON DUPLICATE KEY UPDATE

При вставке записей можно включать режим ON DUPLICATE KEY UPDATE. Для этого четвертым аргументом нужно передать 'DUPLICATE', а второй и третий должны быть составлены как для случая обычного UPDATE:

$data_columns = [
    "description" => "Самый лучший чайник",
    "price" => 3500,
];
$unique_key_columns = [
    "id" => 3
];
mysql_write_row('goods', $data_columns, $unique_key_columns, 'DUPLICATE');

Такой код приведёт к запросу

INSERT INTO goods SET
    # здесь поля и из $data_columns, и из $unique_key_columns
    id = 3,
    description = 'Самый лучший чайник',
    price = 3500
ON DUPLICATE KEY UPDATE
    # здесь - только поля из $data
    description = 'Самый лучший чайник',
    price = 3500

Бывают ситуации, когда удобнее передать все необходимые данные в $data, а в $unique_keys - указать, какие ключи из них относятся к уникальным полям:

$all_data = [
    "id" => 3,
    "description" => "Самый лучший чайник",
    "price" => 3500
];
$unique_keys = ['id']; // будет исключен из части ON DUPLICATE KEY UPDATE

mysql_write_row('goods', $all_data, $unique_keys, 'DUPLICATE');
// Приведёт к такому же запросу, как в предыдущем примере

INSERT IGNORE и REPLACE

Вставку записи можно проводить также в режимах INSERT IGNORE и REPLACE — их в таком случае нужно указать четвертым аргументом5:

mysql_write_row('goods', $data, FALSE, 'IGNORE');
mysql_write_row('goods', $data, FALSE, 'REPLACE');

Вставка DateTime

Объекты DateTime автоматически преобразуются в строку в формате ДДДД-ММ-ГГ чч:мм:сс. Следующий код:

mysql_write_row(
    'sometable',
    [
        'when' => new DateTime()
    ]
);
Вызовет запрос
INSERT INTO sometable SET when = '2022-09-02 18:43:00'

Вставка JSON

Если элемент является массивом, mysql_write_row() попытается записать его в качестве JSON. При этом возможны два режима работы: первый - запись всего поля целиком, второй - обновление определенного ключа в нём.

Предположим, имеется вот такая таблица:

CREATE TABLE `t` (
  i int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  j json DEFAULT NULL
)

Вставляем в таблицу данные:

$data = [
    'j' => [
        'a' => 10,
        'b' => 'строка'
    ]    
];

mysql_write_row('t', $data);

Результат:

mysql> SELECT * FROM t;
+---+--------------------------------+
| i | j                              |
+---+--------------------------------+
| 1 | {"a": 10, "b": "строка"}       |
+---+--------------------------------+

Преобразование массива в JSON-строку происходит в режиме JSON_UNESCAPED_UNICODE.

Теперь представим, что нам нужно обновить значение элемента a. Если мы передадим функции массив вида [ 'j' => [ 'a' => 11 ] ], поле перепишется полностью:

$id = 1;
$data = [
    'j' => [
        'a' => 11
    ]    
];

mysql_write_row('t', $data, [ 'i' => $id ] );

Результат:

mysql> SELECT * FROM t;
+---+-----------+
| i | j         |
+---+-----------+
| 1 | {"a": 11} |
+---+-----------+

Чтобы затронуть только значение a, нужно применить другую форму записи, переместив название JSON-ключа в имя поля:

$id = 1;
$data = [
    'j.a' => 11
];

mysql_write_row('t', $data, [ 'i' => $id ] );

В этом случае значения соседних элементов сохранятся:

mysql> SELECT * FROM t;
+---+--------------------------------+
| i | j                              |
+---+--------------------------------+
| 1 | {"a": 11, "b": "строка"}       |
+---+--------------------------------+

mysql_escape()

Функция mysql_escape() экранирует переменную, учитывая её тип:

  • строка будет экранирована6 и заключена в одинарные кавычки
  • число будет оставлено без изменений
  • вместо NULL в строку запроса будет вставлено выражение NULL
  • во всех остальных случаях применяется функция intval()

Это позволяет экранировать все переменные единообразно:

$id = 5;
$name = "O'Reilly";

$sql = "
    SELECT ...
    WHERE id = "
. mysql_escape($id) . "
      AND name = "
. mysql_escape($name) . ";
    "
;
// В результате получится запрос
// SELECT ...
// WHERE id = 5
//   AND name = 'O\'Reilly'

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

$values = array(10, NULL, "строка", "Д'Артаньян");

$sql = "SELECT ... WHERE col IN (" . mysql_escape($values) . ")";
// Получится запрос
// SELECT ... WHERE col IN (10, NULL, 'строка', 'Д\'Артаньян')

Поддержка mysqli

Изначально функции были написаны на основе оригинального API MySQL, но они могут работать и в режиме совместимости с «улучшенным» модулем — mysqli. Добиться этого очень просто: нужно, чтобы к моменту вызова функций в глобальной области видимости существовала переменная $mysqli, содержащая соответствующий объект:

$mysqli = mysqli_connect(...); // можно и объектный синтаксис - не важно
// дальше всё как обычно
mysql_q("SELECT 1");

Никаких других дополнительных действий не требуется.

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

2. А также отправляет веб-клиенту HTTP-заголовок с кодом 500, который сигнализирует о том, что на сервере произошел сбой.

3. Вид сообщений об ошибках таков и для других функций; речь о них пойдет в статье далее.

4. Столбец, указываемый во втором аргументе, обязательно должен присутствовать в результате запроса. Если для этого столбца используется псевдоним, то вторым аргументом нужно указывать именно псевдоним:

mysql_getcolumn("SELECT id AS alias, name FROM products", "alias")

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

6. Экранирование проводится с помощью встроенных функций библиотек MySQL — mysql_real_escape_string или mysqli_real_escape_string() (в зависимости от того, включен ли режим совместимости с модулем mysqli).


© Все права на данную статью принадлежат порталу webew.ru. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в печатных изданиях допускается только с разрешения редакции.
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
NO USERPIC

rgbeast

Про debug_backtrace() не знал. Интересно.
10.10.2010, 18:21
Ответить

1234ru

Вот тут обсуждали
http://webew.ru/posts/1584.webew
кстати, с твоим участием :)
То, что не убивает нас, делает нас инвалидами.
10.10.2010, 23:40
Ответить
NO USERPIC

afobaz

материал конечно интересный, но все функции не опишешь (при необходимости надо заглядывать в справочник), с другой стороны - это и не нужно, так как большинство используется очень редко
13.03.2011, 16:03
Ответить

1234ru

afobaz, а какие все? Не совсем понял, про какие функции в данном случае идет речь.
То, что не убивает нас, делает нас инвалидами.
15.03.2011, 17:35
Ответить
NO USERPIC

demetrius

Действительно, функции удобны! Спасибо автору за некоторое облегчение процесса программирования!
Русского человека не оцифруешь!
16.12.2012, 17:36
Ответить

1234ru

Библиотека дополнена следующими возможностями:

- замена меток в запросе на параметры из массива

- поддержка mysqli
То, что не убивает нас, делает нас инвалидами.
29.03.2013, 17:10
Ответить
NO USERPIC

rgbeast

Замена меток - отличная опция. Удивляюсь, что этого до сих пор нет в большинстве клиентских библиотек.
29.03.2013, 17:32
Ответить

1234ru

Библиотека дополнена функцией mysql_write_row() для записи и обновления одиночных строк (альтернатива написанию INSERT/UPDATE вручную)
То, что не убивает нас, делает нас инвалидами.
11.07.2013, 20:33
Ответить

1234ru

В функцию mysql_write_row() добавлена возможность работы в режимах INSERT IGNORE, REPLACE и ON DUPLICATE KEY UPDATE.
То, что не убивает нас, делает нас инвалидами.
05.08.2013, 20:53
Ответить

1234ru

При замене меток теперь можно использовать не только скалярные величины, но и массивы - подробнее см. раздел статьи про подстановку.
То, что не убивает нас, делает нас инвалидами.
23.08.2013, 14:24
Ответить

wwwplaton

Подскажите возможно ли получить масив вида:

array(
    array(
        array('id'=>'1', 'name'=>'Вася'),
        array('id'=>'2', 'name'=>'Петя'),
        array('id'=>'3', 'name'=>'Федя')
    ),
    array(
        array('id'=>'4', 'name'=>'...'),
        array('id'=>'5', 'name'=>'...'),
        array('id'=>'6', 'name'=>'...')
    ),
    array(
        array('id'=>'7', 'name'=>'...'),
        array('id'=>'8', 'name'=>'...'),
        array('id'=>'9', 'name'=>'...')
    )
);


Пытаюсь сделать что то из mysql_gettable, но ума явно не хватает :(
20.09.2013, 19:12
Ответить

1234ru

Строго говоря, вам нужно на каждую из трех групп сделать вызов mysql_gettable().

Если задача состоит в том, чтобы получить для каждого из элементов некоего списка множество значений из другого (хороший пример - список заказов, каждому из которых может соответствовать несколько товарных позиций), то я бы на вашем месте одним запросом получил соазу весь второй список (для всех значений из первого), а потом прошел бы по нему циклом и разложил их по нужным записям из первого.
Так будет быстрее, потому что вместо неизвестно скольки запросов к базе данных (зависит от длины первого списка) их будет всего два.
То, что не убивает нас, делает нас инвалидами.
20.09.2013, 20:41
Ответить

wwwplaton

этот масив пример вывода с одной таблицы сортировка по ID
+ то что и нужно мне сделать это разбить записи на группы. К примеру в масиве разбито по 3 записи.
в итоге получить
1,2,3
4,5,6
7,8,9

я понимаю как это сделать, но и за плохого знания php и его синтаксиса не могу : (
20.09.2013, 20:50
Ответить

1234ru

Тогда нужно сначала получить все записи в одном массиве, а потом еще раз пройти циклом и разбить их по три. Что-то типа

$sql = "..."; // тут запрос

$triplets = array();

$n = 0;
foreach (mysql_gettable($sql) as $row) {
    $i = floor($n/3);
    $triplets[$i][] = $row;
    $n++;
}
То, что не убивает нас, делает нас инвалидами.
24.09.2013, 12:06
Ответить
NO USERPIC

grytskiv

Все просто!

$new_array = array_chunk($old_array, 3);
14.10.2013, 02:54
Ответить

1234ru

Добавил описание функции mysql_escape() (присутствует в библиотеке с некоторых пор).
То, что не убивает нас, делает нас инвалидами.
18.12.2013, 11:13
Ответить

Yuriy

Привет, Михаил. Интересно следить за усовершенствованием твоих разработок. Нашёл кое-какие ошибки:

Функция mysql_write_row()

1. В блоке "INSERT ... ON DUPLICATE KEY UPDATE" почти в самом конце надо, наверное, исправить на

$result = mysql_q($sql, $all_data);


2. Теоретически функцию можно использовать для обновления не только по уникальному полю (или нескольким полям, составляющим уникальный ключ), но и при обновлении, где любые поля в условии WHERE перечисляются через AND.

Допустим, требуется выполнить запрос

UPDATE tablename SET f1=11, f2=22 WHERE f2=2 AND f3=3


с помощью функции mysql_write_row():

$set_keys = [
    'f1'=>11,
    'f2'=>22
];
$where_keys = [
    'f2'=>2,
    'f3'=>3
];
mysql_write_row('tablename', $set_keys, $where_keys);


В функции в блоке "обычный UPDATE" есть такая часть:

$result = mysql_q($sql, $data + $unique_key);


Сумма $data + $unique_key равна
[
    'f1'=>11,
    'f2'=>22,
    'f3'=>3
]


Поэтому выполнится запрос:

UPDATE tablename SET f1=11, f2=22 WHERE f2=22 AND f3=3


А это не то, что нужно.

Поэтому, думаю, что формирование части запроса "после WHERE" нужно немного переделать:

foreach ($unique_key as $key => $value)
    $sql .= " $key = ? AND ";


а получение результата сделать таким:

$result = mysql_q($sql, $data + array_values($unique_key));


Либо экранирование с подстановкой сделать в самой функции mysql_write_row, а при вызове mysql_q не передавать ей массив, либо передавать только $data (если оставить формирование части SET как есть).

3. MySQL позволяет создавать поля, состоящие из одних цифр, в этом случае будут формироваться неправильные запросы и на обновление, и на вставку
UPDATE ... WHERE 2 = 456
INSERT ... SET 1=123, 2=456

Поэтому названия полей лучше бы, наверное, заключать в специальные кавычки ``. Хотя мало кто, наверное, использует такие поля.

Кроме того будет не так как нужно работать сложение вот в этом месте (обычный UPDATE (усовершенствованный)):
$result = mysql_q($sql, $data + array_values($unique_key));


В этом случае можно:

Цитата:

экранирование с подстановкой делать в самой функции mysql_write_row, а при вызове mysql_q не передавать ей массив, либо передавать только $data.
02.04.2014, 13:38
Ответить

Yuriy

Ещё обнаружил такую особенность при тестировании функций.

Просто напишу примеры.

1.
UPDATE tablename
SET title = ?
WHERE id = ?


$substitutions = [
    'Что? Где? Когда?',
    4
];
mysql_q($sql, $substitutions);


Запрос получается таким:

UPDATE tablename
SET title = 'Что4 Где? Когда?'
WHERE id = ?


2. Такого вида запросы, когда знак вопроса присутствует не в качестве плейсхолдера,
а уже является частью данных в запросе:

UPDATE tablename
SET title = "Что? Где? Когда?"
WHERE id = ?


mysql_q($sql, 2);


Знак вопроса заменится не там, где надо.

Те же проблемы и с именованными плейсхолдерами, правда вероятность нахождения в массиве или исходных данных части строк вида ":fieldname" гораздо ниже. Но всё же.

Вообще, раньше изучал (но не пользовался) похожий инструмент плейсхолдеров. Называется DBSimple.
http://dklab.ru/lib/DbSimple/manual.html
Может быть, там можно почерпнуть какие-то принципы и использовать в данных функциях.
02.04.2014, 14:20
Ответить

Yuriy

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

Пример не очень удачный, но показывает суть:

SELECT * FROM users WHERE login = ? AND password = ?


$login = 'admin';
$pass = 0;
$row = mysql_getrow($sql, [$login, $pass]);

var_dump($row);


Результат:

array (size=3)
  'user_id' => int 123
  'login' => string 'admin' (length=5)
  'password' => string 'fgdfgwrety856gf' (length=15)



В общем, запрос срабатывает не так как нужно, если для текстового поля вместо текстового значения подставлять число.
Можно конечно, в коде использовать приведение переменной к строковому типу:

$row = mysql_getrow($sql, [$login, (string)$pass]);


Но это очень неудобно да и можно забыть каждый раз приводить к строке.
03.04.2014, 12:40
Ответить

1234ru

Yuriy
В блоке "INSERT ... ON DUPLICATE KEY UPDATE" почти в самом конце надо, наверное, исправить на

$result = mysql_q($sql, $all_data);


Да, так будет правильнее.
Исправил.
То, что не убивает нас, делает нас инвалидами.
03.04.2014, 18:22
Ответить

1234ru

Yuriy
Теоретически функцию можно использовать для обновления не только по уникальному полю (или нескольким полям, составляющим уникальный ключ), но и при обновлении, где любые поля в условии WHERE перечисляются через AND.

Допустим, требуется выполнить запрос

UPDATE tablename SET f1=11, f2=22 WHERE f2=2 AND f3=3


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

С другой стороны, почему бы и нет.
Действительно, в случае UPDATE логично и наглядно в одном аргументе перечислять поля для SET, а в другом - для WHERE.

Сделал. Теперь код

Yuriy
$set_keys = [
    'f1'=>11,
    'f2'=>22
];
$where_keys = [
    'f2'=>2,
    'f3'=>3
];
mysql_write_row('tablename', $set_keys, $where_keys);


даст нужный запрос:

UPDATE tablename SET f1=11, f2=22 WHERE f2=2 AND f3=3
То, что не убивает нас, делает нас инвалидами.
03.04.2014, 18:51
Ответить

1234ru

Yuriy
MySQL позволяет создавать поля, состоящие из одних цифр, в этом случае будут формироваться неправильные запросы и на обновление, и на вставку


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

mysql_write_row( 'tablename', array('`1`' => 123, '`2`' => 456) )


даст

INSERT INTO tablename SET `1` = 123, `2` = 456
То, что не убивает нас, делает нас инвалидами.
03.04.2014, 19:35
Ответить

1234ru

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

Для меток вида :name это исправлено - теперь код вида

$data = array(
        'title' => 'Тут метка, которая не должна замениться - :id',
        'id' => 5,
    );

mysql_q("INSERT INTO tablename SET id = :id, title = :title", $data);


даст правильный запрос:

INSERT INTO table SET id = 5, title = Тут метка, которая не должна замениться - :id



Что касается меток вида "?". Чтобы исправить их замену, нужно писать код дополнительно.
На мой взгляд, такие метки - вещь практически бесполезная: для подстановки как правило используется ассоциативный массив, причину же использовать числовой в современном PHP представить сложно; запрос с ними не выглядит наглядным.
Поддержку их я включил в библиотеку лишь потому, что для этого потребовались две лишние строчки кода. Но, как выяснилось, этого оказалось недостаточно. Усложнять код библиотеки в данном случае я считаю нецелесообразным, поэтому принял решение поддержку меток вида "?" отозвать.
То, что не убивает нас, делает нас инвалидами.
04.04.2014, 03:08
Ответить

1234ru

Yuriy
Кажется, в некоторых случаях плейсхолдеры данных функций небезопасны.


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

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


А чему тут удивляться, если ты сам передал вместо строки число? Выполни такой запрос в любом клиенте MySQL - получишь такой же результат. Отсюда вывод, что запрос как раз отрабатывает так, как нужно, и подобное поведение вызвано не ошибкой библиотеки, а неверным использованием средств СУБД.


Yuriy
Можно конечно, в коде использовать приведение переменной к строковому типу ... Но это очень неудобно да и можно забыть каждый раз приводить к строке.


Допустим, стоит задача сделать "удобно" и избавить от необходимости следить за типом. Этого можно добиться двумя способами:

а) все параметры перед подстановкой приводить к какому-то одному типу (например, строковому)

б) явно указывать каждому параметру, к какому типу его приводить при подстановке

Вариант а) ограничивает возможности и будет приводить к ошибкам (например, когда вместо переданного числа будет подставляться строка, к которой это число приведено), поэтому его использовать не стоит.

Вариант б) потребует еще более громоздкого и запутанного кода, чем явное приведение типов: будет то же самое приведение, но только в виде указания дополнительного параметра у каждого из элементов массива замены.

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


А вообще непонятно, откуда взяться в таком случае числам. PHP, в основном, имеет дело со строками: запросы к БД возвращают ячейки в виде строк (даже для числовых полей), параметры HTTP-запросов (в т.ч. данные форм) также приходят в виде строк.

Так или иначе, не стоит считать за труд написание такого кода, когда строке сопоставляется строка, а числу - число.
То, что не убивает нас, делает нас инвалидами.
04.04.2014, 03:46
Ответить

Yuriy

Цитата:
Выполни такой запрос в любом клиенте MySQL - получишь такой же результат.


Да, это я понимаю - строковое поле и переданное число сравниваются как вещественные числа.

Цитата:
А вообще непонятно, откуда взяться в таком случае числам.


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

$mixed_val = some_func_return_mixed_result($param); // возвращает либо строку, либо число


Нужно получить из базы записи строго соответствующие строковому представлению переменной.
Записывая запрос вручную, переменная явно заключается в апострофы:

SELECT * FROM tablename WHERE fieldname = '$mixed_val'


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

SELECT * FROM tablename WHERE fieldname = :mixed_val

$row = mysql_gettable($sql, false, [ 'mixed_val' => (STRING)$mixed_val ]); // приводим в типу STRING


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

SELECT * FROM table
WHERE ...
LIMIT :offset, :per_page


Если параметр per_page приходит из $_GET, то нужно обязательно преобразовать к числовому типу. Хотя и без этого контроль значения этих переменных должен быть произведён до передачи их в запрос. Таким образом, даже и не требуется их передавать в функции, а можно подставлять в строку запроса до передачи запроса в функцию.

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

А текущий вариант функций мне и самому нравится - лаконичное соответствие между типами PHP и MySQL, может быть так оно и лучше. :)
04.04.2014, 21:15
Ответить

Yuriy

Ошибка - не эскейпятся метки

function($matches) use ($substitutions) {
    return $substitutions[$matches{1}];
},
04.04.2014, 23:52
Ответить

1234ru

Ага.
Исправил.
То, что не убивает нас, делает нас инвалидами.
05.04.2014, 00:29
Ответить

1234ru

Yuriy
SELECT * FROM table
WHERE ...
LIMIT :offset, :per_page

К слову. Насчет offset и per_page - посмотри сюда.

Yuriy
А текущий вариант функций мне и самому нравится

Это самое главное :)
То, что не убивает нас, делает нас инвалидами.
05.04.2014, 02:01
Ответить

Yuriy

Спасибо, про ORM читал обе статьи - пытаюсь как раз вникнуть.
05.04.2014, 21:27
Ответить

1234ru

В версию 1.35 внесены небольшие исправления по работе с mysqli.
То, что не убивает нас, делает нас инвалидами.
03.03.2015, 15:24
Ответить

1234ru

В версии 1.40 код, выполняющий подстановку параметров в запрос, вынесен в отдельную функцию — mysql_substitute.
То, что не убивает нас, делает нас инвалидами.
16.03.2015, 12:30
Ответить

1234ru

С версии 1.41 функция mysql_write_row() заключает переданные имена колонок (они же - ключи массива данных) в обратные апострофы (`).
То, что не убивает нас, делает нас инвалидами.
27.10.2015, 12:30
Ответить
NO USERPIC

smartcore

Я так понимаю, что при работе в режиме mysqli prepared statements не используются?
03.03.2016, 11:53
Ответить

1234ru

Для подстановки параметров в запрос - нет.
Если на ваш взгляд prepared statements чем-то лучше, советую вам почитать вот эту ветку обсуждения.
То, что не убивает нас, делает нас инвалидами.
03.03.2016, 14:19
Ответить

1234ru

С версии 1.42 в случае ошибки MySQL генерируется ошибка типа E_USER_ERROR (используется trigger_error()).
То, что не убивает нас, делает нас инвалидами.
18.10.2016, 16:17
Ответить
NO USERPIC

smartcore

Может быть, имеет в смысл добавить в ф-цию mysql_write_row проверку на заполненность $data? Просто если в силу каких-л. причин ее не передать, в ф-цию mysql_q передаются запросы вида "INSERT INTO table SE"
18.12.2016, 20:39
Ответить

1234ru

Теоретически может быть ситуация, когда в таблицу с автоинкрементом нужно записать "пустую" строку. Если запретить вызывать функцию с пустым $data, то это станет невозможно.

А вообще я думаю, что проверка должна быть перед вызовом mysql_write_row().
Если $data может иметь переменный состав и тем более быть пустым, то наверняка перед записью надо проверить, что там всё как положено.
То, что не убивает нас, делает нас инвалидами.
19.12.2016, 11:54
Ответить
NO USERPIC

smartcore

Проблема в том что сейчас в случае, если $data пуст, в результате выполнения строк ф-ции mysql_write_row:
$sql .= " INTO $tablename SET ";
   
foreach ($data as $key => $value)
    $sql .= "`$key` = :$key, ";
       
$sql = substr($sql, 0, -2); // убираем запятую и пробел

Мы получаем ЗАВЕДОМО НЕКОРРЕКТНЫЙ с т.з. синтаксиса запрос "INSERT INTO $tablename SE"
19.12.2016, 12:25
Ответить

1234ru

Ваша правда.
Исправил, ссылка на обновленную версию файла - в статье.
То, что не убивает нас, делает нас инвалидами.
19.12.2016, 16:12
Ответить

1234ru

С версии 1.46 mysql_q() в случае ошибки отправляет веб-клиенту заголовок с HTTP-кодом 500.
То, что не убивает нас, делает нас инвалидами.
31.07.2019, 17:20
Ответить

1234ru

С версии 1.47 mysql_write_row() поддерживает запись в поля типа JSON.
То, что не убивает нас, делает нас инвалидами.
06.10.2020, 15:12
Ответить
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
© 2008—2024 webew.ru, связаться: x собака webew.ru
Сайт использует Flede и соответствует стандартам WAI-WCAG 1.0 на уровне A.
Rambler's Top100

Реклама: