Код шаблона для выполнения хранимых процедур - PullRequest
1 голос
/ 25 сентября 2019

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

// Exception Handler
class customException extends Exception {}

// Database Link (include file in a private directory)
function db_connect()
{
    $hostname = "localhost";
    $username = "username";
    $password = "password";
    $database = "database";

    $connection = mysqli_connect($hostname, $username, $password, $database);

    return $connection;
}

// Template for calling common types of stored procedures:
// select a table row based on the primary key (pk)
function select_pk($connection, string $pk): array
{
    // if other database is needed
    mysqli_select_db($connection, "database1");

    // query execution
    $query = sprintf("CALL select__pk('%s')", mysqli_real_escape_string($connection, $pk));
    $resource = mysqli_query($connection, $query);
    $result = mysqli_fetch_assoc($resource);

    // prepare for next query
    mysqli_free_result($resource);      
    while(mysqli_more_results($connection)) mysqli_next_result($connection);

    // use exception handling if necessary
    if(!isset($result)) throw new customException('pk not found');

    return $result;
}   

// Typical execution
$connection = db_connect();

try
{
    $result = select_pk($connection, $pk);
}
catch(customException $e)
{
    /*** do something ***/
}

Хотя этот шаблондо сих пор, работая нормально (один сервер), у меня сложилось впечатление, что:

  • подготовка к следующему запросу слишком сложна (mysqli_free_result, mysqli_more_results и mysqli_next_result)
  • он неправильно обрабатывает ошибки

Вопрос

Любые комментарии или советы по улучшению этого шаблона?

1 Ответ

2 голосов
/ 25 сентября 2019

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

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

Mysqli connection

У меня есть сомнения по поводу сохранения кода подключения в функции.Он просит, чтобы его неправильно использовали.Соединение с одной базой данных должно быть строго установлено один раз во время одного экземпляра HTTP-запроса / php.но цель функции вызываться несколько раз.Вместо этого было бы лучше поместить код подключения в файл, а затем просто включить этот файл в свой код в одном месте.

У меня есть канонический код подключения mysqli , которыйрешает множество проблем еще до того, как они появятся.Итак, вместо function db_connect() давайте создадим файл с именем mysqli.php и поместим туда следующий код

<?php

$host = '127.0.0.1';
$db   = 'test';
$user = 'root';
$pass = '';
$charset = 'utf8mb4';

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
    $conn = new mysqli($host, $user, $pass, $db);
    $conn->set_charset($charset);
} catch (\mysqli_sql_exception $e) {
     throw new \mysqli_sql_exception($e->getMessage(), $e->getCode());
}
unset($host, $db, $user, $pass, $charset); // we don't need them anymore

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

Выполнение подготовленных запросов

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

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

Поэтому следующим решением будет вспомогательная функция, подобная этой

function prepared_query($mysqli, $sql, $params = [], $types = "")
{
    if (!$params) {
        return $mysqli->query($sql);
    }
    $types = $types ?: str_repeat("s", count($params));
    $stmt = $mysqli->prepare($sql);
    $stmt->bind_param($types, ...$params);
    $stmt->execute();
    return $stmt->get_result();
}

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

Вызов хранимых процедур с помощью mysqli

Хранимые процедуры не просты из-за причуды: каждый вызов возвращает более одного набора результатов , и поэтому нам нужно их циклически обойти.Мы не можем избежать этого, но, по крайней мере, мы можем автоматизировать и этот процесс.Мы можем написать функцию, которая инкапсулирует весь набор результатов jiggery-pokery.

function prepared_call($mysqli, $sql, $params = [], $types = ""): array
{
    $resource = prepared_query($mysqli, $sql, $params, $types);
    $data = $resource->fetch_all(MYSQLI_ASSOC);
    while(mysqli_more_results($mysqli)) mysqli_next_result($mysqli);
    return $data;
}

Специальная функция для вызова PK

И, наконец, мы можем переписать вашу select_pk() функцию

function select_pk($mysqli, string $pk): array
{
    $data = prepared_call($mysqli, "CALL select__pk(?)", $pk);
    return $data[0] ?? null;
}   

Я не совсем уверен, что нам нужнаисключение здесь:

include 'mysqli.php';

$result = select_pk($mysqli, $pk);
if (!$result) {
    /*** do something ***/
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...