← Lekcje
PHP · lekcja 20

Bazy danych 3/5: zapisujemy dane formularza do bazy

Dzisiaj:

  1. Dane z $_POST trafiają do zapytania INSERT
  2. Tabela products dostaje nowy wiersz - kolumna id wypełnia się sama
  3. Bezpieczny sposób: zapytanie przygotowane zamiast sklejania tekstu
  4. Ćwiczenie: formularz dopisuje nowy produkt do bazy shop
Rozgrzewka · 3 minuty

Rozgrzewka: SELECT i wynik zapytania

  1. Co robi zapytanie SELECT na tabeli - co przez nie „przechodzi”?
  2. Co zwraca mysqli_query(), gdy zapytanie jest typu SELECT?
  3. Skąd pętla while z mysqli_fetch_assoc() wie, kiedy się zatrzymać?
Część 1 · Od formularza do zapytania

Od $_POST do zapytania INSERT

Formularz wysyła dane metodą POST. W PHP trafiają one do tablicy $_POST, pod kluczami takimi jak atrybuty name pól formularza. Z tych wartości budujemy zwykły tekst zapytania INSERT. Wykonujemy go tą samą funkcją mysqli_query(), którą znamy z SELECT - tym razem zapytanie nie zwraca żadnych wierszy.

$_POST z formularza productName = "Ołówek" productPrice = "1.80" tekst zapytania ($query) INSERT INTO products (name, price) VALUES ('Ołówek', '1.80') (zwykły string sklejony z $_POST) mysqli_query($db, $query) wykonuje INSERT na tabeli zwraca: true
PHP Manual: „Returns false on failure. […] For other successful queries, mysqli_query() will return true.” (php.net/manual/en/mysqli.query.php)
Część 2 · Tabela przed i po

Tabela products: przed i po INSERT

INSERT nie zmienia wierszy, które już są w tabeli - dokłada jeden nowy wiersz na końcu. Kolumny name i price dostają wartości z formularza.

przed id name price 1 Kubek 19.90 2 Długopis 3.50 3 Zeszyt 7.20 INSERT po id name price 1 Kubek 19.90 2 Długopis 3.50 3 Zeszyt 7.20 4 Ołówek 1.80 nowy wiersz dopisany na dole
MariaDB Server docs: instrukcja INSERT dokłada wiersze do tabeli (mariadb.com/docs/server/reference/sql-statements/data-manipulation/insert)
Część 3 · Klucz główny sam się wypełnia

Kolumna id: autoinkrementowany klucz główny

Kolumna id to klucz główny: liczba, która jednoznacznie oznacza każdy wiersz w tabeli - żadne dwa wiersze nie mają tej samej wartości. Do tej kolumny nie wpisujemy nic (albo wpisujemy NULL), bo ma atrybut AUTO_INCREMENT: MariaDB sama nadaje jej kolejną wolną liczbę. Numer właśnie dodanego wiersza można od razu odczytać funkcją mysqli_insert_id().

INSERT INTO products VALUES (NULL, 'Ołówek', '1.80') id = NULL - „dobierz sam” AUTO_INCREMENT ostatni id w tabeli: 3 nowy id: 3 + 1 id = 4 nowy wiersz w tabeli mysqli_insert_id($db) odczytana zaraz po INSERT zwraca: 4
PHP Manual: „Returns the ID generated by an INSERT or UPDATE query on a table with a column having the AUTO_INCREMENT attribute.” (php.net/manual/en/mysqli.insert-id.php)
Część 4 · Bezpieczny sposób

Bezpieczny sposób: zapytanie przygotowane

Zapytanie sklejone wprost z $_POST (jak na poprzednich slajdach) jest ryzykowne: „SQL injection is a technique where an attacker exploits flaws in application code responsible for building dynamic SQL queries” (PHP Manual). Po polsku: SQL injection to atak, w którym ktoś wpisuje w pole formularza fragment kodu SQL, żeby zmienić działanie naszego zapytania i np. zobaczyć albo skasować cudze dane. Bezpieczny sposób to zapytanie przygotowane - zamiast wklejać dane w tekst, wstawiamy znak ? jako miejsce na wartość, a dane podstawia osobno mysqli_stmt_bind_param().

// dane nigdy nie wchodzą wprost do tekstu zapytania
$stmt = mysqli_prepare($db, "INSERT INTO products (name, price) VALUES (?, ?)");
mysqli_stmt_bind_param($stmt, "sd", $name, $price);
mysqli_stmt_execute($stmt);

"sd" mówi PHP, jakiego typu są podstawiane wartości: s = string ($name), d = liczba zmiennoprzecinkowa ($price).

Ciekawostka: PHP Manual ilustruje ryzyko SQL injection słynnym komiksem xkcd numer 327 - matka specjalnie nazwała syna Robert'); DROP TABLE Students;--, licząc na to, że taki napis skasuje tabelę uczniów w szkolnej bazie danych. W komiksie chłopiec dostaje przez to przydomek „Little Bobby Tables”.

PHP Manual: mysqli.prepare.php („markers are legal only in certain places… permitted in the VALUES() list of an INSERT statement”), mysqli-stmt.bind-param.php (typy „s” i „d”), security.database.sql-injection.php („SQL injection is a technique where an attacker exploits flaws in application code responsible for building dynamic SQL queries”) · ciekawostka: PHP Manual, security.database.sql-injection.php (obrazek z podpisem „Image courtesy of » xkcd”, link do xkcd.com/327) · xkcd nr 327 „Exploits of a Mom” (transkrypcja: explainxkcd.com/wiki/index.php/327:_Exploits_of_a_Mom)
Egzamin INF.03

Tak pytają na egzaminie

1. Przedstawiony fragment kodu PHP ma za zadanie umieścić dane znajdujące się w zmiennych $a, $b, $c w bazie danych, w tabeli dane. Tabela dane zawiera cztery pola, z czego pierwsze to autoinkrementowany klucz główny. Które z poleceń powinno być przypisane do zmiennej $query?

  1. SELECT '$a', '$b', '$c' FROM dane;
  2. SELECT NULL, '$a', '$b', '$c' FROM dane;
  3. INSERT INTO dane VALUES ('$a', '$b', '$c');
  4. INSERT INTO dane VALUES (NULL, '$a', '$b', '$c');

2. W tabeli pracownicy zdefiniowano klucz główny typu INTEGER z atrybutami NOT NULL oraz AUTO_INCREMENT, a także pola imie i nazwisko. Po wykonaniu INSERT INTO pracownicy (imie, nazwisko) VALUES ('Anna', 'Nowak');, czyli kwerendy pomijającej pole klucza, w bazie danych MySQL nastąpi

  1. błąd nieprawidłowej liczby pól.
  2. zignorowanie polecenia, tabela pozostanie bez zmian.
  3. wpisanie rekordu do tabeli, dla klucza głównego zostanie przydzielona wartość NULL.
  4. wpisanie rekordu do tabeli, dla klucza głównego zostanie przydzielona kolejna wartość naturalna.
CKE, EE.09, styczeń 2020, zadanie 25 · CKE, E.14, styczeń 2020, zadanie 25
Ćwiczenie przy komputerze · 18 minut

Ćwiczenie: formularz dodaje produkt

  1. W folderze swojego serwera (np. htdocs) utwórz plik add-product.html: formularz z dwoma polami <input name="productName"> i <input name="productPrice">, action="add-product.php", method="post".
  2. Utwórz add-product.php: połącz się z bazą shop jak w connect-database.php, odbierz $_POST["productName"] i $_POST["productPrice"].
  3. Zbuduj tekst "INSERT INTO products (name, price) VALUES ('...', '...')" z tych wartości i wykonaj mysqli_query().
  4. Otwórz localhost/add-product.html, wypełnij formularz i sprawdź w phpMyAdmin, że nowy wiersz doszedł do tabeli products z własnym numerem id.
  5. Gotowe pliki add-product.html i add-product.php zostaw w folderze kursu.
Na koniec

Sprawdź się

  1. Jaką wartość zwraca mysqli_query() po udanym zapytaniu INSERT (bez wyników do pobrania)?
  2. Dlaczego do kolumny id nie wpisujemy nic, tylko NULL - kto nadaje jej właściwą wartość?
  3. Czym różni się zapytanie przygotowane od zapytania sklejonego z tekstu i dlaczego jest bezpieczniejsze?

Na następnej lekcji: zmieniamy i usuwamy dane poleceniami UPDATE i DELETE.

Poza szkołą

A jak to wygląda w produkcji?

Na lekcjiW produkcjiStatus
Zapytanie INSERT sklejone z tekstu i $_POSTZapytania przygotowane (mysqli_prepare + bind_param, albo PDO) - dane nigdy nie wchodzą wprost do tekstu zapytania.nadal w użyciu (dobra praktyka)
Dane z formularza wpisywane do bazy bez sprawdzeniaWalidacja i „sanityzacja” wejścia (typ, długość, zakres) przed każdym zapisem - często w osobnej warstwie (frameworku).nadal w użyciu
Jeden plik PHP łączy formularz, zapytanie i wypisywanie wynikuFramework (np. Laravel) rozdziela to na kontroler, model i widok (wzorzec MVC).zastępowane (frameworkiem)
mysqli_insert_id() odczytane ręcznie po każdym INSERTORM zwraca gotowy obiekt nowego rekordu z już wypełnionym id.zastępowane (frameworkiem)
PHP Manual: mysqli.query.php, mysqli.insert-id.php, security.database.sql-injection.php