CRUD to skrót od czterech podstawowych operacji na danych: Create (dodaj), Read (odczytaj), Update (zmień) i Delete (usuń). Prawie każda aplikacja — katalog książek, lista zadań, dziennik ocen — jest w istocie zestawem tych czterech operacji z interfejsem. Na lekcji uczniowie piszą pięć funkcji obsługujących tabelę książek biblioteki, a formularze i strony dołożą na kolejnych zajęciach.
Scenariusz jest przeznaczony na 45 minut dla klasy znającej funkcje i PDO — na przykład po lekcjach o funkcjach w PHP i zapytaniach przygotowanych. Do ćwiczeń nie potrzeba serwera bazy danych: PDO łączy się z SQLite w pamięci.
Przygotowanie stanowisk
Potrzebny jest PHP 8.x uruchamiany w terminalu (na przykład z pakietu XAMPP; sprawdź polecenie php -v). Rozpakuj pakiet z sekcji materiałów do osobnego folderu i przejdź do niego w terminalu. Skrypty uruchamiasz poleceniem php start.php; serwer WWW nie jest potrzebny, a wynik pojawia się w terminalu. Jeśli pobierasz pojedyncze pliki .php.txt ze strony zamiast ZIP, usuń końcówkę .txt lokalnie.
Cele i plan lekcji
Po zajęciach uczeń potrafi:
- wskazać cztery operacje CRUD i odpowiadające im instrukcje SQL (
INSERT,SELECT,UPDATE,DELETE); - napisać funkcje CRUD z zapytaniami przygotowanymi, typami parametrów i typem wyniku;
- zwrócić
nullalbofalse, gdy rekord nie istnieje, i rozpoznać taką sytuację w kodzie wywołującym; - sprawdzić liczbę zmienionych wierszy (
rowCount) i ostatni identyfikator (lastInsertId).
| Czas | Praca na lekcji |
|---|---|
| 0–5 min | Problem: cztery czynności na danych |
| 5–15 min | Tabela i funkcje Create oraz Read |
| 15–22 min | Funkcje Update i Delete |
| 22–37 min | Cztery zadania samodzielne |
| 37–42 min | Przypadki brzegowe: nieistniejący identyfikator |
| 42–45 min | Pytania kontrolne |
CRUD w pięciu funkcjach
| Operacja | SQL | Funkcja | Zwraca |
|---|---|---|---|
| Create | INSERT | dodaj($pdo, $tytul, $autor, $rok) | identyfikator nowej książki |
| Read (lista) | SELECT | wszystkie($pdo) | tablica książek |
| Read (jedna) | SELECT ... WHERE id | znajdz($pdo, $id) | tablica albo null |
| Update | UPDATE ... WHERE id | zmien($pdo, $id, $tytul) | true, gdy coś zmieniono |
| Delete | DELETE ... WHERE id | usun($pdo, $id) | true, gdy coś usunięto |
Wszystkie zapytania są przygotowane (prepare i execute), więc dane nie mogą zmienić struktury SQL. Typy parametrów i wyniku (int, string, ?array, bool) w połączeniu z declare(strict_types=1) wcześnie wykrywają błędy. $pdo->lastInsertId() zwraca identyfikator wstawionego wiersza (kolumna AUTOINCREMENT nadaje je automatycznie), a rowCount() — liczbę wierszy zmienionych przez UPDATE lub DELETE; wartość 0 oznacza, że rekordu o podanym id nie było. Funkcja fetch zwraca false, gdy nie ma wiersza — zamieniamy to na null, który w typie ?array oznacza „brak”.
W aplikacji z MySQL zmienia się tylko połączenie (new PDO("mysql:host=...;dbname=...;charset=utf8mb4", ...) i AUTO_INCREMENT zamiast AUTOINCREMENT); cała reszta kodu pozostaje taka sama.
Zadania
Zadanie 1. Create. Napisz dodaj i dodaj trzy książki (w tym jedną z apostrofem w tytule). Wypisz zwrócone identyfikatory.
Zadanie 2. Read. Napisz wszystkie i znajdz. Wypisz wszystkie książki w formacie id. tytuł (autor, rok), tytuł książki nr 2 oraz wynik wyszukania książki nr 99 (var_dump).
Zadanie 3. Update. Napisz zmien i zmień tytuł książki nr 2. Sprawdź wynik dla nieistniejącej książki nr 99 i odczytaj zmieniony tytuł.
Zadanie 4. Delete. Napisz usun i usuń książkę nr 1 dwa razy. Wyjaśnij, dlaczego drugie usunięcie zwraca false, i wypisz liczbę pozostałych książek.
Dla chętnych: sprawdź, jaki identyfikator dostanie kolejny wiersz po usunięciu ostatniego (AUTOINCREMENT kontra zwykły PRIMARY KEY).
Rozwiązanie i wyniki
<?php
declare(strict_types=1);
$pdo = new PDO("sqlite::memory:");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec("CREATE TABLE ksiazki (id INTEGER PRIMARY KEY AUTOINCREMENT, tytul TEXT NOT NULL, autor TEXT NOT NULL, rok INTEGER NOT NULL)");
function dodaj(PDO $pdo, string $tytul, string $autor, int $rok): int
{
$stmt = $pdo->prepare("INSERT INTO ksiazki (tytul, autor, rok) VALUES (:tytul, :autor, :rok)");
$stmt->execute([":tytul" => $tytul, ":autor" => $autor, ":rok" => $rok]);
return (int) $pdo->lastInsertId();
}
function wszystkie(PDO $pdo): array
{
return $pdo->query("SELECT id, tytul, autor, rok FROM ksiazki ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
}
function znajdz(PDO $pdo, int $id): ?array
{
$stmt = $pdo->prepare("SELECT id, tytul, autor, rok FROM ksiazki WHERE id = :id");
$stmt->execute([":id" => $id]);
$wiersz = $stmt->fetch(PDO::FETCH_ASSOC);
return $wiersz === false ? null : $wiersz;
}
function zmien(PDO $pdo, int $id, string $tytul): bool
{
$stmt = $pdo->prepare("UPDATE ksiazki SET tytul = :tytul WHERE id = :id");
$stmt->execute([":tytul" => $tytul, ":id" => $id]);
return $stmt->rowCount() > 0;
}
function usun(PDO $pdo, int $id): bool
{
$stmt = $pdo->prepare("DELETE FROM ksiazki WHERE id = :id");
$stmt->execute([":id" => $id]);
return $stmt->rowCount() > 0;
}
// Zadanie 1 (Create)
echo dodaj($pdo, "Kod w praktyce", "Nowak", 2024), " ";
echo dodaj($pdo, "Zielony balkon", "Kowal", 2022), " ";
echo dodaj($pdo, "O'Reilly i przyjaciele", "Lis", 2020), "\n";
// Zadanie 2 (Read)
foreach (wszystkie($pdo) as $k) {
echo $k["id"], ". ", $k["tytul"], " (", $k["autor"], ", ", $k["rok"], ")\n";
}
echo znajdz($pdo, 2)["tytul"], "\n";
var_dump(znajdz($pdo, 99));
// Zadanie 3 (Update)
var_dump(zmien($pdo, 2, "Zielony balkon - wydanie 2"));
var_dump(zmien($pdo, 99, "Nie ma"));
echo znajdz($pdo, 2)["tytul"], "\n";
// Zadanie 4 (Delete)
var_dump(usun($pdo, 1));
var_dump(usun($pdo, 1));
echo count(wszystkie($pdo)), " ksiazki zostaly\n";
Program wypisze:
1 2 3
1. Kod w praktyce (Nowak, 2024)
2. Zielony balkon (Kowal, 2022)
3. O'Reilly i przyjaciele (Lis, 2020)
Zielony balkon
NULL
bool(true)
bool(false)
Zielony balkon - wydanie 2
bool(true)
bool(false)
2 ksiazki zostaly
Po usunięciu ostatniego wiersza AUTOINCREMENT nie używa ponownie jego identyfikatora — dlatego w zadaniu dla chętnych kolejny wiersz dostaje numer 3, a nie 2. Zadanie dla chętnych (rozszerzenie.php) wypisuje:
3
Typowe błędy
| Objaw | Co sprawdzić |
|---|---|
Call to a member function fetch() on false | Zapytanie się nie powiodło; ustaw PDO::ERRMODE_EXCEPTION, aby zobaczyć komunikat błędu. |
znajdz zwraca false zamiast null | Nie zamieniono wyniku fetch na null w typie ?array. |
UPDATE zmienia wszystkie książki | Brak WHERE id = :id. |
rowCount() nie zgadza się z oczekiwaniem | rowCount jest wiarygodny dla UPDATE i DELETE; wiersz zmieniony na tę samą wartość może nie być policzony w niektórych bazach. |
TypeError przy wywołaniu funkcji | Przekazano tekst tam, gdzie oczekiwano liczby (tryb strict_types). |
| Apostrof w tytule psuje zapytanie | Dane doklejono do tekstu SQL zamiast przekazać je przez execute. |
Pytania kontrolne i punktacja
- Rozwiń skrót CRUD i podaj odpowiadające operacjom instrukcje SQL.
- Co oznacza
rowCount() == 0poDELETE? - Dlaczego
znajdzzwracanull, a niefalse?
Odpowiedzi: Create — INSERT, Read — SELECT, Update — UPDATE, Delete — DELETE. Że nie było wiersza o podanym id, więc nic nie usunięto. Bo typ wyniku ?array dopuszcza tablicę lub null, co jednoznacznie oznacza brak rekordu.
Proponowana punktacja (razem 10 punktów): zadanie 1 — dodaj z zapytaniem przygotowanym 2 pkt, trzy wywołania 1 pkt; zadanie 2 — wszystkie i znajdz 2 pkt, obsługa braku rekordu 1 pkt; zadanie 3 — zmien z rowCount 2 pkt; zadanie 4 — usun 1 pkt, wyjaśnienie 1 pkt. Zadanie dla chętnych nie jest wymagane do pełnej oceny.
Materiały do pobrania
Rozpakuj ZIP przed otwarciem plików. Materiały są dostępne bez logowania; publiczne rozwiązanie służy nauce i samodzielnemu sprawdzeniu, a nie poufnemu sprawdzianowi.
Co dalej?
Następny krok to formularze HTML, które wywołują te funkcje, oraz uwierzytelnianie użytkowników. Ten materiał domyka dział o bazach w PHP w kursie PHP w TablicaPro, gdzie znajdziesz prezentacje, ćwiczenia i testy do prowadzenia lekcji.
Źródła techniczne: PDO w podręczniku PHP oraz CRUD w Wikipedii. Zobacz też lekcję o walidacji danych w PHP.
Poprowadź kolejne lekcje PHP
Pełny kurs: prezentacje, ćwiczenia i testy do pracy z klasą.
Kup dostęp do kursu PHP →