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=?

update-delete.phpphp
<?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=?
  1. SET wymienia zmieniane kolumny. WHERE zawęża operację do ID oraz właściciela z sesji. Bez WHERE aktualizowałbyś wiele wierszy.
  2. bind_param sii łączy tytuł oraz dwie liczby. ID użytkownika nie może pochodzić z ukrytego pola formularza.
  3. DELETE usuwa cały wiersz, a nie wartość jednej kolumny. Nie wywołuj go samym linkiem GET.
  4. 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

ZapisZnaczeniePrzykł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 / throwtry 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.

Ta interakcja wymaga włączonego JavaScriptu.

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

public/edytuj.phpphp
<?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

public/usun.phpphp
<?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.

Oczekiwany rezultat
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ć

  1. Zmienisz lub usuniesz tylko własny rekord i sprawdzisz ochronę także przy bezpośrednim żądaniu.
  2. Potrafisz wskazać w kodzie przyczynę wyniku i zaplanować próbę błędnych danych.
  3. 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ę →
Źródła i dalsza nauka