19. PHP – skrypty zmieniające dane w bazie danych


Rozszerzamy skrypt z poprzednich zajęć o możliwość dodawania i usuwania rekordów w bazie.

Przypominam, że w poniższym kodzie nie dokonaliśmy jeszcze zabezpieczeń odbieranych z formularza danych.

Należy dokonać dodatkowo walidacji odbieranych danych, która uchroni nas przed atakami SQL Injection i XSS, a zapytania do bazy najlepiej opracowywać z wykorzystaniem Prepared Statements, o których trochę niżej.

Do istniejącego formularza dodajemy 2 nowe inputy: Dodai i Usuń, następnie odbieramy dane i dokonujemy na ich podstawie zmian w bazie. 

<?php
//ustawiamy dane do bazy
$serwer = "localhost";
$user = "root";
$pass = "";
$base = "operacje_db";

mysqli_report(MYSQLI_REPORT_OFF);$db = @new mysqli($serwer, $user, $pass, $base);

if ($db->connect_error) {
    die('Błąd połączenia z bazą danych: ' . $db->connect_error);
} else {
    echo "Połączenie nawiązano<br>";
    if (isset($_POST['przycisk'])) { //sprawdzamy czy kliknięto wyszukaj
        $sql = "SELECT * FROM `test` WHERE 1";
        if (!empty($_POST['imie'])) $sql .= " && imie='{$_POST['imie']}'";
        if (!empty($_POST['nazwisko'])) $sql .= " && nazwisko='{$_POST['nazwisko']}'";
        echo $sql . "<br>";
        $wynik = $db->query($sql);
        foreach ($wynik as $row) {
            echo "Witaj {$row['imie']} {$row['nazwisko']}<br>";
        }
    } elseif (isset($_POST['add'])) { //sprawdzamy czy kliknięto dodaj
        if (!empty($_POST['imie']) && !empty($_POST['nazwisko'])) {
            $sql = "INSERT INTO `test`(`imie`, `nazwisko`) VALUES ('{$_POST['imie']}','{$_POST['nazwisko']}')";
            $wynik = $db->query($sql);
            //echo $db->affected_rows . ' ' . $db->insert_id;
            $ar = $db->affected_rows;
            if ($ar != 0) echo "Dodano {$_POST['imie']} {$_POST['nazwisko']}";
        } else {
            echo "Uzupełnij pola.";
        }
    } elseif (isset($_POST['del'])) { //sprawdzamy czy kliknięto usuń
        if (!empty($_POST['imie']) && !empty($_POST['nazwisko'])) {
            $sql = "DELETE FROM `test` WHERE imie='{$_POST['imie']}' && nazwisko='{$_POST['nazwisko']}'";
            $wynik = $db->query($sql);
            //echo $db->affected_rows . ' ' . $db->insert_id;
            $ar = $db->affected_rows;
            if ($ar != 0) echo "Usunięto rekordy w ilości: $ar o wartościach: {$_POST['imie']} {$_POST['nazwisko']}";
        } else {
            echo "Uzupełnij pola.";
        }
    } else {
        echo "Wykonaj akcję.";
    }
}

$db->close();
?>

<form action="" method="post">
    Imię: <input type="text" name="imie">
    Nazwisko: <input type="text" name="nazwisko">
    <input type="submit" value="Wyszukaj" name="przycisk">
    <input type="submit" value="Dodaj" name="add">
    <input type="submit" value="Usuń" name="del">
</form>

 

Wykorzystanie „bindowania”, czyli parameterized prepared statements – bezpieczeństwo przede wszystkim

Sparametryzowane zapytania pozwalają na wykonywanie kodu SQL zabezpieczonego przed SQL Injection.

Działa to w ten sposób, że najpierw przygotowujemy zapytanie, czyli do bazy wysyłany jest szablon kwerendy, w którym zamiast zmiennych podajemy znaki zapytania – „?”. Na tej podstawie serwer inicjuje swoje wewnętrzne zasoby do późniejszego wykorzystania.

W kolejnym kroku „bindujemy”, czyli wiązemy ze sobą wartości parametrów (każdemu znakowi zapytania przypisujemy zmienną) i wysyłamy je na serwer.

Na koniec zamykamy parametryzację, by zwolnić zasoby.

Ale dlaczego to takie bezpieczne?

Zbindowane zmienne są wysyłane do serwera niezależnie od zapytania, serwer używa tych wartości tylko w momencie wykonania, po przeanalizowaniu szablonu instrukcji. Parametry nigdy nie są bezpośrednio podstawiane w ciągu zapytania. Należy dostarczyć do serwera wskazówkę dotyczącą typu zmiennej powiązanej, aby utworzyć odpowiednią konwersję.

Przykład podstawowego użycia prepare statements

<?php
//ustawiamy dane do bazy
$serwer = 'localhost'; //lub 127.0.0.1 - to jest nazwa serwera  
$user = 'root'; //użytkownik bazy danych
$pass = ''; //hasło do bazy
$baza = 'operacje_db'; //nazwa bazy

//łączenie z bazą
mysqli_report(MYSQLI_REPORT_OFF);$db = @new mysqli($serwer, $user, $pass, $baza); //podejście obiektowe - korzystamy z klasy PHP o nazwie mysqli

//sprawdzamy czy się łączy w podejściu obiektowym
if ($db->connect_error) {
    die('Błąd połączenia: ' . $db->connect_error);
} else {
    echo 'Połączenie nawiązano<br />';
    if (isset($_POST['add'])) {
        $imie = !empty($_POST['imie']) ? $_POST['imie'] : 'nobody';
        $nazwisko = !empty($_POST['nazwisko']) ? $_POST['nazwisko'] : 'nobody';
        //przygotowujemy zapytanie
        $sql = "INSERT INTO `test`(`imie`, `nazwisko`) VALUES (?, ?)";
        $stmt = $db->prepare($sql);
        //bindujemy parametry
        $stmt->bind_param("ss", $imie, $nazwisko); //ss - oznacza string string
        //wykonujemy zapytanie
        $stmt->execute();
        echo "Dodano do bazy: $imie $nazwisko";
    } else {
        echo "Prześlij formularz.";
    }
}

$db->close(); //zamykamy połączenie
?>

<form action="" method="POST">
    Imię: <input type="text" name="imie">
    Nazwisko: <input type="text" name="nazwisko">
    <input type="submit" name="add" value="Dodaj">
</form>

Przykład

<?php
//ustawiamy dane do bazy
$serwer = 'localhost'; //lub 127.0.0.1 - to jest nazwa serwera  
$user = 'root'; //użytkownik bazy danych
$pass = ''; //hasło do bazy
$baza = 'operacje_db'; //nazwa bazy

//łączenie z bazą
mysqli_report(MYSQLI_REPORT_OFF);$db = @new mysqli($serwer, $user, $pass, $baza); //podejście obiektowe - korzystamy z klasy PHP o nazwie mysqli

//sprawdzamy czy się łączy w podejściu obiektowym
if ($db->connect_error) {
    die('Błąd połączenia: ' . $db->connect_error);
} else {
    $imie = !empty($_POST['imie']) ? htmlspecialchars(trim($_POST['imie'])) : false;
    $nazwisko = !empty($_POST['nazwisko']) ? htmlspecialchars(trim($_POST['nazwisko'])) : false;
    if (isset($_POST['add'])) {
        if ($imie && $nazwisko) {
            //----------------------------------------------------------------
            //przygotowujemy zapytanie
            $sql = "INSERT INTO `test`(`imie`, `nazwisko`) VALUES (?, ?)";
            $stmt = $db->prepare($sql);
            //bindujemy parametry
            $stmt->bind_param("ss", $imie, $nazwisko); //ss - oznacza string string, drugi parametr musi być zmienną (nie można wpisać bezpośrednio wartości)

            //wykonujemy zapytanie
            $stmt->execute();
            //----------------------------------------------------------------
            /*
            //tak możemy zobaczyć co jest w środku $stmt
            echo '<pre>';
            print_r($stmt);
            echo '</pre>';
            */

            if ($stmt->affected_rows != 0) echo "Dodano do bazy: $imie $nazwisko";
            else echo "Nie dodano wiersza";
        } else {
            echo "Uzupełnij wszystkie pola";
        }
    } elseif (isset($_POST['search'])) { //w zależności od uzupełnionego pola w formularzu
        if ($imie || $nazwisko) {
            $sql = "SELECT * FROM `test` WHERE 1";
            //przygotowujemy dane do bindowania
            $b_data_types = '';
            $b_values = array();
            if (!empty($imie)) {
                $sql .= " && imie=?";
                $b_data_types .= 's';
                array_push($b_values, $imie);
            }

            if (!empty($nazwisko)) {
                $sql .= " && nazwisko=?";
                $b_data_types .= 's';
                array_push($b_values, $nazwisko);
            }

            //----------------------------------------------------------------
            //przygotowujemy zapytanie
            $stmt = $db->prepare($sql);
            $stmt->bind_param($b_data_types, ...$b_values);
            $stmt->execute();
            $wynik = $stmt->get_result();
            foreach ($wynik as $row) {
                echo "Witaj {$row['imie']} {$row['nazwisko']}<br>";
            }
        } else {
            echo "Wypełnij przynajmniej jedno pole";
        }
    } elseif (isset($_POST['all'])) { //wyświetl wszystkie
        $sql = "SELECT * FROM `test`";
        //----------------------------------------------------------------
        //przygotowujemy zapytanie
        $stmt = $db->prepare($sql);
        $stmt->execute();
        $wynik = $stmt->get_result();
        foreach ($wynik as $row) {
            echo "Witaj {$row['imie']} {$row['nazwisko']}<br>";
        }
    } else {
        echo "Prześlij formularz.";
    }
}

$db->close(); //zamykamy połączenie
?>

<form action="" method="POST">
    Imię: <input type="text" name="imie">
    Nazwisko: <input type="text" name="nazwisko">
    <input type="submit" name="add" value="Dodaj">
    <input type="submit" name="all" value="Wyświetl wszystko">
    <input type="submit" name="search" value="Wyszukaj">
</form>