PHP · projekt CRUD

Projekt PHP — aplikacja CRUD do zarządzania książkami

Create, Read, Update i Delete w pięciu funkcjach PDO na tabeli książek. Cztery zadania z zapytaniami przygotowanymi i przypadkami brzegowymi.

45 minutPoziom podstawowyPliki i rozwiązania

Materiał redakcyjny TablicaPro · liceum i technikum

$stmt = $pdo->prepare("DELETE FROM ksiazki WHERE id = :id");
$stmt->execute([":id" => 1]);
var_dump($stmt->rowCount() > 0);
bool(true) a potem bool(false)
Drugie usunięcie nic nie zmienia.

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ć null albo false, gdy rekord nie istnieje, i rozpoznać taką sytuację w kodzie wywołującym;
  • sprawdzić liczbę zmienionych wierszy (rowCount) i ostatni identyfikator (lastInsertId).
CzasPraca na lekcji
0–5 minProblem: cztery czynności na danych
5–15 minTabela i funkcje Create oraz Read
15–22 minFunkcje Update i Delete
22–37 minCztery zadania samodzielne
37–42 minPrzypadki brzegowe: nieistniejący identyfikator
42–45 minPytania kontrolne

CRUD w pięciu funkcjach

OperacjaSQLFunkcjaZwraca
CreateINSERTdodaj($pdo, $tytul, $autor, $rok)identyfikator nowej książki
Read (lista)SELECTwszystkie($pdo)tablica książek
Read (jedna)SELECT ... WHERE idznajdz($pdo, $id)tablica albo null
UpdateUPDATE ... WHERE idzmien($pdo, $id, $tytul)true, gdy coś zmieniono
DeleteDELETE ... WHERE idusun($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

ObjawCo sprawdzić
Call to a member function fetch() on falseZapytanie się nie powiodło; ustaw PDO::ERRMODE_EXCEPTION, aby zobaczyć komunikat błędu.
znajdz zwraca false zamiast nullNie zamieniono wyniku fetch na null w typie ?array.
UPDATE zmienia wszystkie książkiBrak WHERE id = :id.
rowCount() nie zgadza się z oczekiwaniemrowCount 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 funkcjiPrzekazano tekst tam, gdzie oczekiwano liczby (tryb strict_types).
Apostrof w tytule psuje zapytanieDane doklejono do tekstu SQL zamiast przekazać je przez execute.

Pytania kontrolne i punktacja

  1. Rozwiń skrót CRUD i podaj odpowiadające operacjom instrukcje SQL.
  2. Co oznacza rowCount() == 0 po DELETE?
  3. Dlaczego znajdz zwraca null, a nie false?

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 →

Zobacz program kursu