INF.03 · Aplikacje internetowe PHP
Edycja i usuwanie danych przez PHP — pełny CRUD
CRUD z kontrolą właściciela.
- PHP8.3
- mysqli · MariaDB
- Praktyka i diagnoza
Po tej lekcji: Zmienisz lub usuniesz tylko własny rekord i sprawdzisz ochronę także przy bezpośrednim żądaniu.
Zrozum mechanizm
CRUD oznacza Create/Read/Update/Delete. GET może pokazać formularz edycji, ale zmiany wykonuj przez POST z tokenem. Najpierw potwierdź logowanie, potem waliduj ID i dane. W zapytaniu zawrzyj jednocześnie id rekordu oraz wlasciciel_id z sesji. To broni przed zmianą cudzego rekordu po podmianie ID. Sam ukryty przycisk nie chroni serwera.
UPDATE z tymi samymi wartościami może mieć affected_rows=0 — to nie dowód braku rekordu. Dlatego wcześniej odczytujemy rekord z kontrolą właściciela. W DELETE dokładnie jeden usunięty rekord oznacza sukces; brak własnego rekordu daje 404. Klucz obcy chroni historię zamówień: filmu należącego do zamówienia nie usuwamy, aplikacja odpowiada 409.
Jak uruchamiać krótkie przykłady?
Każdy krótki przykład oznaczony własnym plikiem uruchamiaj osobno, nie doklejaj wszystkich definicji do jednego pliku. W edytorze zapisz go jako .php, a w terminalu tego folderu wpisz np. php for.php. Wtedy pokazane końce wierszy PHP_EOL są widoczne. W odpowiedzi HTML znak końca wiersza sam nie robi nowej linii na ekranie: użyj <br> albo znacznika <pre>. Wyświetl źródło pozwala zobaczyć tekst odpowiedzi.
Przykłady opisane jako fragment projektu wymagają paczki KINO / LAB i wcześniejszej konfiguracji. Nie uruchamiaj ich jako samodzielnych endpointów. Pełny plik niżej pokazuje kontekst. Funkcje h(), pole() i wymagajPost() są nasze; ich definicje wczytuje bootstrap.php.
UPDATE i DELETE: zmieniasz wyłącznie wskazany własny rekord
Wcześniej pokaż formularz GET, a po poprawnym POST zmień rekord. WHERE jest częścią reguły bezpieczeństwa, nie tylko filtrem wyglądu strony.
Podstawowa forma: UPDATE tabela SET kolumna=? WHERE id=? AND wlasciciel_id=?
<?php
// Wycinki obsługi POST po logowaniu, CSRF i walidacji:
$zmiana = $polaczenie->prepare('UPDATE Filmy SET tytul=? WHERE id=? AND wlasciciel_id=?');
$zmiana->bind_param('sii', $tytul, $id, $idUzytkownika);
$zmiana->execute();
// ALTERNATYWNA operacja w osobnym usun.php, nie zaraz po UPDATE:
// DELETE FROM Filmy WHERE id=? AND wlasciciel_id=?
- SET wymienia zmieniane kolumny. WHERE zawęża operację do ID oraz właściciela z sesji. Bez WHERE aktualizowałbyś wiele wierszy.
- bind_param sii łączy tytuł oraz dwie liczby. ID użytkownika nie może pochodzić z ukrytego pola formularza.
- DELETE usuwa cały wiersz, a nie wartość jednej kolumny. Nie wywołuj go samym linkiem GET.
- affected_rows mówi o liczbie zmienionych wierszy, ale 0 po UPDATE może znaczyć, że podano te same dane. Projekt najpierw potwierdza istnienie własnego rekordu.
Jak czytać pozostałe zapisy w pełnym przykładzie
| Zapis | Znaczenie | Przykład użycia |
|---|---|---|
pole($dane, $nazwa) | Nasza funkcja: pobiera wskazany klucz tylko wtedy, gdy ma wartość string; inaczej oddaje pusty napis. isset sprawdza obecność i brak null, is_string typ. | pole($_POST, "email") |
wymagajPost() | Nasza funkcja: sprawdza metodę POST oraz CSRF; przy błędzie przerywa odpowiedź z405/403. | wymagajPost(); |
wymagajLogowania() | Nasza funkcja: oddaje int ID konta z sesji lub przekierowuje do logowania. | $id = wymagajLogowania(); |
przekieruj($adres) | Nasza funkcja wysyła Location ze statusem303 i kończy wykonanie przez exit. Typ wyniku never oznacza, że nie wraca do dalszej instrukcji. | przekieruj("/panel.php"); |
naglowek($tytul), stopka() | Nasze funkcje wypisujące wspólny HTML strony. void oznacza brak zwracanego wyniku do użycia w wyrażeniu. | naglowek("Katalog"); |
dirname(__DIR__) | dirname daje katalog nadrzędny; __DIR__ to katalog bieżącego pliku. W public prowadzi to do katalogu projektu, a dopisana ścieżka wskazuje src. | dirname(__DIR__)."/src/bootstrap.php" |
try / catch / throw | try obejmuje próbę, catch przechwytuje wyjątek, throw go zgłasza; $blad jest obiektem błędu. W mysqli raportowanie wyjątków trzeba włączyć. | try { /* operacja */ } catch (Throwable $blad) { /* obsługa */ } |
===, !==, &&, ||, ! | Identyczne, nieidentyczne, i, lub, zaprzeczenie. Porównanie ścisłe obejmuje typ; &&/|| nie sprawdzają drugiej części, gdy wynik jest już rozstrzygnięty. | $rok === false || $rok < 1888 |
-> oraz :: | -> wybiera właściwość lub metodę obiektu; :: odnosi się do elementu statycznego klasy. To nie jest zapis dostępu do tablicy. | $zapytanie->execute(); |
Sprawdź w działaniu
CRUD z kontrolą właściciela — sprawdź w modelu
Model w przeglądarce wyjaśnia mechanizm na fikcyjnych danych. Nie wykonuje PHP ani zapytań SQL i nie tworzy kont. Prawdziwe skrypty uruchomisz lokalnie z paczki.
Przewidź rezultat, wykonaj próbę i wyjaśnij, która decyzja po stronie serwera decyduje o wyniku.
Uruchom krok po kroku
Otwórz edytuj.php?id=ID własnego filmu, zmień tytuł, a potem zapisz ponownie bez zmian. Obie próby mają działać. Zaloguj się jako drugi fikcyjny użytkownik i podmień ID na cudzy — otrzymasz 404. Wywołaj usun.php przez GET jako zalogowany:405 i brak zmiany. Formularz POST bez tokenu ma dać 403.
Plik wzorcowy: public/edytuj.php
<?php
require_once dirname(__DIR__).'/src/db.php';
require_once dirname(__DIR__).'/src/dane-filmu.php';
$idUzytkownika = wymagajLogowania();
$zrodlo = $_SERVER['REQUEST_METHOD'] === 'POST' ? $_POST : $_GET;
$id = filter_var(pole($zrodlo, 'id'), FILTER_VALIDATE_INT);
if ($id === false || $id < 1) {
http_response_code(422);
exit('Nieprawidłowe ID.');
}
$odczyt = $polaczenie->prepare('SELECT id, tytul, rok, gatunek, cena_grosze FROM Filmy WHERE id=? AND wlasciciel_id=?');
$odczyt->bind_param('ii', $id, $idUzytkownika);
$odczyt->execute();
$film = $odczyt->get_result()->fetch_assoc();
if (! $film) {
http_response_code(404);
exit('Nie znaleziono Twojego filmu.');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
wymagajPost();
[$tytul, $rok, $gatunek, $cena] = daneFilmu($_POST);
$zmiana = $polaczenie->prepare('UPDATE Filmy SET tytul=?, rok=?, gatunek=?, cena_grosze=? WHERE id=? AND wlasciciel_id=?');
$zmiana->bind_param('sisiii', $tytul, $rok, $gatunek, $cena, $id, $idUzytkownika);
$zmiana->execute();
przekieruj('/panel.php');
}
naglowek('Edytuj swój film');
formularzFilmu($film, '/edytuj.php');
stopka();
Osobny endpoint usunięcia — tylko POST
<?php
require_once dirname(__DIR__).'/src/db.php';
$idUzytkownika = wymagajLogowania();
wymagajPost();
$id = filter_var(pole($_POST, 'id'), FILTER_VALIDATE_INT);
if ($id === false || $id < 1) {
http_response_code(422);
exit('Nieprawidłowe ID.');
}
$zapytanie = $polaczenie->prepare('DELETE FROM Filmy WHERE id=? AND wlasciciel_id=?');
$zapytanie->bind_param('ii', $id, $idUzytkownika);
try {
$zapytanie->execute();
} catch (mysqli_sql_exception $blad) {
if ($blad->getCode() !== 1451) {
throw $blad;
}
http_response_code(409);
exit('Film należy do zapisanego zamówienia i nie może zostać usunięty.');
}
if ($zapytanie->affected_rows !== 1) {
http_response_code(404);
exit('Nie znaleziono Twojego filmu.');
}
przekieruj('/panel.php');
Pobierz i odtwórz projekt
W „Źródłach i materiałach” na końcu pobierz KINO / LAB.zip. Rozpakuj całość; instrukcja START.txt opisuje konfigurację. Pierwsze 6 przykładów w public/podstawy działa bez bazy. Dalsze wymagają lokalnej MariaDB 11.4, importu schema.sql i lokalnego konta bazy z minimalnymi prawami. Gotowa paczka pokazuje całość, ale w tej lekcji wykonaj opisane kroki i własne zadanie.
Ćwiczenie z pomocą · samodzielna praca
Twoja próba — podpowiedzi otwieraj po kolei
Przetestuj edycję i usuwanie jako właściciel oraz jako inne konto.
Właściciel zmienia swój rekord, inne konto nie może go odczytać do edycji ani zmienić; GET nie usuwa.
? Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź
Odszukaj wskazany plik i zapisz przewidywany rezultat przed zmianą.
Krok 2 — pokaż podpowiedź
Zmień jedną rzecz. Sprawdź metodę żądania, dane i odpowiedź lub wynik programu.
Krok 3 — pokaż podpowiedź
Wykonaj próbę graniczną lub błędną. Porównaj także stan bazy lub sesji, nie tylko komunikat.
Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.
Podsumowanie
Co warto zapamiętać
- Zmienisz lub usuniesz tylko własny rekord i sprawdzisz ochronę także przy bezpośrednim żądaniu.
- Potrafisz wskazać w kodzie przyczynę wyniku i zaplanować próbę błędnych danych.
- Nie oddajesz haseł, pliku config.local.php ani wartości cookie.
Dalej na swoim koncie
Rozgrzewki, quiz i zadania są w biurku ucznia.
Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.
Zaloguj się →