INF.03-04-23.06-SGCzerwiec 2023 · 43 kroki · ok. 87 min · 37 kryteriów z klucza
Rozwiązanie krok po kroku: Portal biblioteki publicznej (Nowoczesny kod (flex, PDO))
To drugi sposób rozwiązania tego samego arkusza (wydanego też pod numerami 06 i 10). Wynik jest ten sam - baza biblioteka z czterema zapytaniami, grafika ze zdublowanym stosem książek i strona biblioteka.php z listą autorów, adresem, formularzem dodawania czytelnika i potwierdzeniem - i tak samo spełnia każde kryterium klucza. Inna jest droga:
flexbox w kolumnach zamiast float: cztery panele stoją w znaczniku <main>, który układa je w kolumny wysokie na 500 px - panel lewy i środkowy wypełniają po jednej, a dwa prawe dzielą trzecią. Szerokości 30%, 30% i 40% zostają na samych panelach - tak ocenia je klucz,
zmienne CSS dla kolorów z arkusza, wiersze formularza w blokach zamiast <br>, pola z required i podpowiedziami autouzupełniania,
PHP na PDO: wszystkie dane strona obsługuje na górze pliku biblioteka.php, przed HTML-em, a w HTML-u zostają tylko pętla i wypisywanie,
sprawdzanie danych z formularza (trim, symbol od 1 do 6 cyfr) i zapytanie przygotowane do INSERT,
wzorzec PRG (Post/Redirect/Get): po zapisie strona przekierowuje sama na siebie, więc odświeżenie nie doda tego samego czytelnika drugi raz,
bezpieczne wypisywanie (htmlspecialchars w funkcji e()) i komunikaty dla niepełnych danych i awarii bazy.
Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafika pochodzą z prawdziwego archiwum materialy4, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 na bazie z arkusza - od Skryptu 2 po wysłaniu formularza z danymi czytelniczki z obrazu 2, Anny Nowakowskiej (obraz nie pokazuje jej symbolu, więc w podglądzie stoi przykładowy 145322).
Jak liczą się punkty. Klucz ma 37 kryteriów (R.1.1 - R.5.7), każde za 1 punkt. Próg zdania to 75%, czyli 28 punktów. Przy każdym kroku jest napisane, które kryteria spełnia.
Suwak i strzałki obok niego - albo ←→ na klawiaturze - przechodzą krok po kroku; wyjaśnienie, pliki i podgląd zmieniają się razem z krokiem.
Lista „Etap” przenosi od razu do wybranego etapu.
„Rozwiązuję samodzielnie” chowa rozwiązanie kroku - najpierw własna próba, potem odsłonięcie.
„Tryb prezentacji” pokazuje same kroki, bez menu strony - na rzutnik.
Etap 1: Baza danych
Kroki 1-7 · po tym etapie masz 6 z 37 kryteriów (pkt)
Krok 1: Baza danych biblioteka
ok. 2 min
Polecenie z arkusza
Utwórz nową bazę danych o nazwie biblioteka
Rozpakuj materialy4.7z z pulpitu (hasło 4rch!wum4x - cyfra 4 zamiast „a”, wykrzyknik zamiast „i”) do folderu z numerem zdającego: biblioteka.sql i biblioteka.png. W phpMyAdmin Nowa → biblioteka albo zapytaniem:
CREATE DATABASE biblioteka CHARACTER SET utf8 COLLATE utf8_polish_ci;
Arkusz nie podaje porównywania, więc wybór należy do Ciebie. Tabele z biblioteka.sql mają zestaw znaków zapisany we własnej definicji (część latin1, część utf8), więc ustawienie bazy ich nie zmienia.
Typowe błędy
Skrypt łączy się z bazą po nazwie - literówka w nazwie bazy zabiera też punkty za część PHP (R.5.1).
Krok 2: Import tabel z pliku biblioteka.sql
ok. 2 min
Polecenie z arkusza
Do bazy zaimportuj tabele z pliku biblioteka.sql, z wcześniej rozpakowanego archiwum
Zaznaczona baza biblioteka, Import → biblioteka.sql → Wykonaj. Zadanie używa pięciu tabel z obrazu 1: autorzy (10), ksiazki (21 tytułów), kategorie (epika, dramat, liryka), czytelnicy (10) i wypozyczenia.
Krok 3: Zrzut ekranu import.jpg
Kryteria:
R.1.1 Wykonano import tabel do bazy danych o nazwie biblioteka , czynność udokumentowano plikiem o nazwie import w formacie JPEG
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie JPEG i nazwij import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań. Na zrzucie powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel
Cały ekran z paskiem zadań, w JPEG, pod nazwą import. Zrzuty zapytań: kwerenda1 - kwerenda4, też w JPEG i z widocznym zapytaniem (phpMyAdmin wyświetla je nad wynikiem). Każde zapytanie skopiuj też do kwerendy.txt.
Typowe błędy
Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany (uwaga w kluczu przy R.1).
Krok 4: Zapytanie 1: autorzy alfabetycznie po nazwisku
Kryteria: ,
R.1.2 Zapisano plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Zapisano zapytanie 1 wybierające jedynie imie i nazwisko dla wszystkich rekordów w tabeli autorzy , alfabetycznie posortowane rosnąco nazwiskami autorów. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko; (opcjonalnie ASC) oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym jest widocznych dokładnie 10 rekordów z imionami i nazwiskami wszystkich autorów w kolejności alfabetycznej
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie pola imie i nazwisko dla wszystkich rekordów w tabeli autorzy, posortowane rosnąco nazwiskami autorów
Dwa pola i ORDER BY nazwisko - rosnąco to kierunek domyślny (ASC można dopisać). Każda klauzula w osobnej linii. Wynik: 10 autorów od Jana Brzechwy do Magdaleny Witkiewicz - w tej kolejności co lista na obrazie 2. Zrzut: kwerenda1.jpg.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT imie, nazwiskoFROM autorzyORDER BY nazwisko;
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: nowa czytelniczka Ewa Kowalska
Kryteria:
R.1.4 Zapisano zapytanie 2 wstawiające do tabeli czytelnicy rekord Ewa Kowalska z kodem 145321, wartość klucza głównego jest nadawana automatycznie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO `czytelnicy`(`imie`, `nazwisko`, `kod`) VALUES ('Ewa', 'Kowalska', '145321'); (możliwe również bez struktury tabeli lub przez przypisania SET) oraz wynik działania zapytania udokumentowano zrzutem ekranu z widocznym komunikatem o pomyślnym wykonaniu zapytania lub tabela z wstawionym wierszem
ok. 2 min
Polecenie z arkusza
Zapytanie 2: wstawiające do tabeli czytelnicy nowy rekord z danymi: Ewa Kowalska z kodem 145321. Wartość klucza głównego nadawana automatycznie
Lista pól bez id - klucz główny ma AUTO_INCREMENT, więc baza sama nada kolejny numer (11). kod to tekst (varchar(6)), więc też w apostrofach. Komunikat „1 wiersz wstawiony” na zrzucie kwerenda2.jpg. To zapytanie wyśle Skrypt 2 - z parametrami w miejscu danych.
Typowe błędy
Własna wartość id nie spełnia warunku „nadawana automatycznie”. Zapytanie wykonane dwa razy doda dwie Ewy Kowalskie.
kwerendy.txtTekstzmienione: 4 linie
-- Zapytanie 1SELECT imie, nazwiskoFROM autorzyORDER BY nazwisko;-- Zapytanie 2INSERT INTO czytelnicy (imie, nazwisko, kod)VALUES ('Ewa', 'Kowalska', '145321');
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: tytuły z kategorii dramat
Kryteria:
R.1.5 Zapisano zapytanie 3 wybierające jedynie pole tytul dla wszystkich utworów w tabeli ksiazki z kategorii dramat. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT tytul FROM ksiazki WHERE id_kategoria = 2; (lub wykorzystując połączenie tabel i nazwę kategorii 'dramat') oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są dokładnie 4 tytuły: Zemsta, Pan Jowialski, Niemcy, Odwiedziny
ok. 2 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie pole tytul dla wszystkich utworów w tabeli ksiazki, należących do kategorii dramat
aliasyk (ksiazki) i kat (kategorie) - krótki przedrostek mówi, z której tabeli pochodzi pole,
JOIN kategorie AS kat ON kat.id = k.id_kategoria - nazwa kategorii jest w osobnej tabeli,
WHERE kat.nazwa = 'dramat' - warunek na nazwie, a nie na numerze 2: zadziała także wtedy, gdy kategorie dostaną inne numery (klucz uznaje oba warianty).
Wynik: 4 tytuły - Zemsta, Pan Jowialski, Niemcy, Odwiedziny. Zrzut: kwerenda3.jpg.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT imie, nazwiskoFROM autorzyORDER BY nazwisko;-- Zapytanie 2INSERT INTO czytelnicy (imie, nazwisko, kod)VALUES ('Ewa', 'Kowalska', '145321');-- Zapytanie 3SELECT k.tytulFROM ksiazki AS kJOIN kategorie AS kat ON kat.id = k.id_kategoriaWHERE kat.nazwa = 'dramat';
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: książki wypożyczone przez czytelnika 2
Kryteria:
R.1.6 Zapisano zapytanie 4 wybierające jedynie pole tytul wszystkich utworów z tabeli ksiazki wypożyczonych przez czytelnika o identyfikatorze 2. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT tytul FROM ksiazki JOIN wypozyczenia ON ksiazki.id = wypozyczenia.id_ksiazka WHERE wypozyczenia.id_czytelnik = 2; (lub INNER JOIN, lub bez JOIN, z porównaniem kuczy w WHERE) oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym widoczne są jedynie tytuły: Zemsta, Brzechwa dzieciom
ok. 3 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie pole tytul wszystkich utworów w tabeli książki, wypożyczonych przez czytelnika o identyfikatorze 2. W zapytaniu 4 należy posłużyć się relacją
JOIN wypozyczenia AS w ON w.id_ksiazka = k.id - relacja książka-wypożyczenie, warunek na czytelniku. Wynik: Zemsta i Brzechwa dzieciom. Czytelnik 2 ma trzy wypożyczenia, ale jedno wskazuje książkę numer 22, której nie ma w tabeli ksiazki - złączenie wewnętrzne takie wiersze pomija. Zrzut: kwerenda4.jpg.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT imie, nazwiskoFROM autorzyORDER BY nazwisko;-- Zapytanie 2INSERT INTO czytelnicy (imie, nazwisko, kod)VALUES ('Ewa', 'Kowalska', '145321');-- Zapytanie 3SELECT k.tytulFROM ksiazki AS kJOIN kategorie AS kat ON kat.id = k.id_kategoriaWHERE kat.nazwa = 'dramat';-- Zapytanie 4SELECT k.tytulFROM ksiazki AS kJOIN wypozyczenia AS w ON w.id_ksiazka = k.idWHERE w.id_czytelnik = 2;
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 8-23 · po tym etapie masz 19 z 37 kryteriów (pkt)
Krok 8: Grafika: płótno 300 px i drugi stos książek
Kryteria:
R.2.1 Grafikę biblioteka.png przekształcono zgodnie z obrazem 3b, są widoczne dwa stosy książek i szerokość wynosi dokładnie 300 px, a wysokość 235 px
ok. 5 min
Polecenie z arkusza
Grafikę biblioteka.png należy rozszerzyć z rozmiaru poziomego 150 px do rozmiaru 300 px, a następnie w pustym obszarze zdublować stos książek zgodnie z obrazem 3b
Grafika jest taka sama jak w wersji podstawowej. W GIMP-ie Obraz → Rozmiar płótna: szerokość 300, wysokość bez zmian (235), obraz przy lewej krawędzi, wszystkie warstwy. Potem kopia stosu (Zaznacz → Wszystko na starym fragmencie, Kopiuj, Wklej jako nową warstwę) przesunięta o 150 px w prawo i Nadpisz biblioteka.png. To rozszerzenie płótna, nie skalowanie - stos nie może się rozciągnąć.
Typowe błędy
Skaluj obraz do 300 px rozciąga stos w poziomie zamiast dodać puste miejsce - R.2.1 wymaga dwóch stosów i wymiarów dokładnie 300 × 235.
R.2.2 Witrynę internetową zapisano w pliku biblioteka .php, zastosowano deklarację języka HTML5 <!DOCTYPE html>, zadeklarowano język witryny polski np. <html lang = "pl"> oraz zastosowano właściwy standard kodowania polskich znaków
ok. 2 min
Polecenie z arkusza
Składa się ze strony o nazwie biblioteka.php. Zapisana w języku HTML5. Ustawiony język zawartości strony na polski. Jawnie zastosowany właściwy standard kodowania polskich znaków
<!DOCTYPE html>, <html lang="pl"> i <meta charset="UTF-8"> jako pierwszy element <head> - razem R.2.2. Plik leży w htdocs XAMPP-a i otwierasz go przez http://localhost/..., inaczej PHP się nie wykona.
R.3.6 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie style.css , formatowanie pochodzi jedynie z tego arkusza Uwaga: kryterium to nie jest spełnione również, gdy zdający źle przypisał co najmniej jeden selektor do bloku (np. kropka zamiast #)
ok. 1 min
Polecenie z arkusza
Arkusz stylów w pliku o nazwie style.css prawidłowo połączony z kodem strony
Nazwa pliku to style.css (inne arkusze tej sesji mają styl.css). Całe formatowanie pochodzi z tego pliku (R.3.6).
Typowe błędy
Klucz odbiera R.3.6 także wtedy, gdy choć jeden selektor nie trafia w blok (np. kropka zamiast #).
Krok 12: Bloki: baner, cztery panele w main, stopka
Kryteria:
R.2.4 Wygląd strony zdefiniowano dzieląc ją na baner, panele lewy, środkowy, prawy górny i dolny, oraz stopkę. Zastosowano znaczniki sekcji
ok. 2 min
Polecenie z arkusza
Podział strony na bloki: na górze baner, poniżej bloki: lewy, środkowy i dwa prawe ułożone jeden pod drugim oraz na dole blok stopki. Podział zrealizowany za pomocą znaczników sekcji tak, aby wygląd układu bloków strony po uruchomieniu w przeglądarce, był zgodny z obrazem 2
Baner <header>, stopka <footer>, a cztery panele - cztery <section> we wspólnym <main>, który w CSS ułoży je w kolumny (R.2.4). Identyfikatory mówią, co jest w panelu:
Zawartość bloku lewego: Nagłówek trzeciego stopnia o treści „Polecamy dzieła autorów:”. Lista numerowana (uporządkowana), której elementy są generowane skryptem 1
<h3> z dwukropkiem i pusta <ol> - elementy <li> wypisze Skrypt 1.
Krok 15: Panel środkowy: adres ze spacją nierozdzielającą
Kryteria:
R.2.9 Zastosowano jedynie znak specjalny w danych adresowych: Książkowice Wielkie (bez zwykłych spacji)
ok. 1 min
Polecenie z arkusza
Zawartość bloku środkowego: Nagłówek trzeciego stopnia o treści: „ul. Czytelnicza 25, Książkowice Wielkie”. Dwa człony nazwy miasta połączone są spacją nierozdzielającą
Encja zamiast spacji między członami nazwy miasta: przeglądarka nie złamie tam wiersza, więc „Książkowice Wielkie” zawsze stoją w jednej linii. Pozostałe spacje zwykłe (R.2.9).
Typowe błędy
Książkowice Wielkie (spacja obok encji) to dwie spacje - R.2.9 przepada.
Krok 16: Panel środkowy: odsyłacz pocztowy i obraz
Kryteria: , ,
R.2.5 Zapisano w banerze nagłówek h1 "Biblioteka w Książkowicach Wielkich", w panelach przynajmniej jeden z nagłówków h3: "Polecamy dzieła autorów:”, "ul. Czytelnicza 25, Książkowice Wielkie", "Dodaj czytelnika” oraz zapisano przynajmniej jeden paragraf
R.2.8 W bloku środkowym umieszczono obraz z pliku biblioteka.png z tekstem alternatywnym "książki"
R.3.5 Napis "Napisz do nas" jest odsyłaczem pocztowym do adresu [email protected], w definicji zastosowano "mailto:"
ok. 2 min
Polecenie z arkusza
Paragraf (akapit) o treści „Napisz do nas”, który jest odsyłaczem pocztowym do adresu [email protected]. Obraz biblioteka.png z tekstem alternatywnym „książki”
Akapit z odnośnikiem mailto: - kliknięcie otwiera program pocztowy z wpisanym adresem (R.3.5). Z <h1> i nagłówkami <h3> to komplet do R.2.5. Pod spodem grafika z tekstem alternatywnym z arkusza (R.2.8).
Krok 17: Panel prawy górny: nagłówek i formularz POST
Kryteria:
R.3.2 Dane z formularza przesyłane są za pomocą metody POST do tego samego pliku (poprawne również bez atrybutu action)
ok. 2 min
Polecenie z arkusza
Zawartość bloku prawego – górnego: Nagłówek trzeciego stopnia o treści „Dodaj czytelnika”. Formularz wysyłający dane do tego samego pliku metodą bezpieczną.
method="post" - „metodę bezpieczną” arkusz rozumie jako POST: dane idą w treści żądania, a nie w adresie strony (w specyfikacji HTTP słowo „bezpieczna” znaczy co innego, ale klucz R.3.2 wymaga POST). Bez action formularz wysyła dane do tego samego pliku.
Typowe błędy
Bez method="post" formularz wysyła dane metodą GET i Skrypt 2, który czyta $_POST, niczego nie dostanie.
R.3.4 Pole edycyjne do wpisania symbolu jest typu numerycznego
ok. 1 min
Polecenie z arkusza
Pole edycyjne typu numerycznego poprzedzone napisem „symbol: ”
type="number" (R.3.4). Symbol trafi do kolumny kod typu varchar(6), więc max="999999" - najwyżej sześć cyfr - a min="0" blokuje liczby ujemne. To wygoda dla użytkownika, nie zabezpieczenie: Skrypt 2 sprawdzi symbol jeszcze raz.
Zawartość bloku prawego – dolnego: efekt działania skryptu 2
Panel status zostaje na razie pusty - potwierdzenie dodania czytelnika wypisze w nim Skrypt 2. Panel jest w kodzie od kroku z blokami, więc ten krok nie zmienia pliku.
Kroki 24-33 · po tym etapie masz 29 z 37 kryteriów (pkt)
Krok 24: Plik style.css: zmienne i krój Verdana
Kryteria:
R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (*) krój czcionki Verdana
ok. 2 min
Polecenie z arkusza
Domyślne formatowanie wszystkich selektorów: krój czcionki Verdana
Cztery kolory z arkusza w zmiennych - #004D40 pojawia się w trzech regułach (baner, stopka, ramka obrazu), #E0F2F1 w dwóch. Selektor * z Verdaną i krojem zapasowym sans-serif (R.4.1).
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu (prawidłowo zastosowano właściwości CSS układające bloki na stronie, np. float i clear albo display z wartością flex lub grid albo position)
ok. 3 min
Polecenie z arkusza
Podział zrealizowany za pomocą znaczników sekcji tak, aby wygląd układu bloków strony po uruchomieniu w przeglądarce, był zgodny z obrazem 2
flex-flow: column wrap - pojemnik flex układa panele z góry na dół, a gdy kolumna się zapełni, zawija resztę do następnej. Kolumna ma wysokość <main>, czyli 500 px:
panel lewy (500 px) wypełnia całą pierwszą kolumnę,
panel środkowy (500 px) - drugą,
dwa prawe po 250 px mieszczą się razem w trzeciej, jeden pod drugim.
Szerokość kolumny wyznacza szerokość panelu, a procent w width liczy się od <main> - czyli 30%, 30% i 40% strony, jak w arkuszu. Stopka stoi pod <main> sama z siebie, bez clear (R.3.1).
Dlaczego nie siatka? W siatce procent szerokości elementu liczy się od jego kolumny: panel z width: 30% w kolumnie szerokiej na 30% strony zająłby 9% strony. Szerokości trzeba by przenieść do grid-template-columns, a klucz z tego arkusza ocenia je na panelach.
Typowe błędy
height: 500px na <main> jest konieczne - bez niego kolumna nie ma końca i wszystkie cztery panele stoją jeden pod drugim.
Krok 32: Obraz: wysokość, kropkowane obramowanie i zaokrąglenie
Kryteria: , ,
R.4.4 Ustawiono wysokość panelu lewego i środkowego 500px, obu prawych 250px, selektora img 150px
R.4.8 Ustawiono obramowanie obrazu 1px linią kropkowaną koloru #004D40
R.4.9 Ustawiono zaokrąglenie rogów dla obramowania obrazu na 5px (np. border-radius)
ok. 2 min
Polecenie z arkusza
Dla selektora obrazu: marginesy wewnętrzne 5 px, wysokość 150 px, obramowanie linią kropkowaną o grubości 1 px i kolorze #004D40 zaokrągloną na rogach o 5 px
height: 150px - szerokość przeliczy się w tej samej proporcji (około 191 px). dotted to linia kropkowana, border-radius zaokrągla rogi ramki. Wszystkie wysokości z arkusza są ustawione (R.4.4).
Kroki 34-41 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 34: Połączenie z bazą na górze pliku i zamknięcie
Kryteria: ,
R.5.1 Zastosowano funkcję realizującą połączenie z serwerem bazy danych na localhost, użytkownik root, bez hasła, baza danych biblioteka
R.5.2 Ostatnią operacją na bazie danych jest jej zamknięcie
ok. 4 min
Polecenie z arkusza
Wymagania dotyczące skryptu: Napisany w języku PHP, umieszczony w pliku biblioteka.php. Skrypt łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie biblioteka. Na końcu działania skrypt zamyka połączenie z serwerem
Całą pracę z bazą strona wykona przed HTML-em, w jednym bloku PHP na samej górze biblioteka.php - w jednym miejscu, a połączenie zamyka się, zanim przeglądarka dostanie pierwszy znacznik. Ten układ przyda się też za chwilę: po zapisie czytelnika strona wyśle przeglądarce przekierowanie, a to da się zrobić tylko przed wypisaniem czegokolwiek.
new PDO(...) - serwer localhost, baza biblioteka, kodowanie utf8mb4, użytkownik root bez hasła (R.5.1). ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek; FETCH_ASSOC - wiersze z nazwami kolumn,
$pdo = null - połączenie PDO zamyka się, gdy znika ostatnie odwołanie do obiektu. Stoi na końcu bloku, więc pozostanie ostatnią operacją na bazie (R.5.2),
try / catch (PDOException ...) - gdy baza nie działa, uczeń zobaczy zwykłe zdanie, a nie komunikat PHP ze szczegółami serwera.
PDO zamiast mysqli z tabeli 1 - klucz przyjmuje obie biblioteki (uwaga przy R.5). Od tego kroku podgląd to wynik prawdziwego uruchomienia strony.
biblioteka.phpPHPzmienione: 12 linii
<?phptry {$pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$databaseError = 'Nie udało się połączyć z bazą. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Biblioteka publiczna</title> <linkrel="stylesheet"href="style.css"></head><body> <header> <h1>Biblioteka w Książkowicach Wielkich</h1> </header> <main> <sectionid="authors"> <h3>Polecamy dzieła autorów:</h3> <ol> </ol> </section> <sectionid="address"> <h3>ul. Czytelnicza 25, Książkowice Wielkie</h3> <p><ahref="mailto:[email protected]">Napisz do nas</a></p> <imgsrc="biblioteka.png"alt="książki"> </section> <sectionid="new-reader"> <h3>Dodaj czytelnika</h3> <formmethod="post"> <div> <labelfor="name">imię: </label> <inputtype="text"id="name"name="name"requiredautocomplete="given-name"> </div> <div> <labelfor="surname">nazwisko: </label> <inputtype="text"id="surname"name="surname"requiredautocomplete="family-name"> </div> <div> <labelfor="code">symbol: </label> <inputtype="number"id="code"name="code"requiredmin="0"max="999999"> </div> <buttontype="submit">DODAJ</button> </form> </section> <sectionid="status"> </section> </main> <footer> <p>Projekt strony: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (biblioteka.php)
Krok 35: Skrypt 1: zapytanie 1
ok. 2 min
Polecenie z arkusza
Skrypt 1: Wysyła do bazy danych zapytanie 1
Zapytanie bez danych od użytkownika, więc wystarczy query. fetchAll() zwraca od razu dziesięć wierszy z kluczami imie i nazwisko. W catch lista dostaje wartość pustą, żeby HTML niżej nie trafił na niezdefiniowaną zmienną. Na stronie jeszcze nic nie widać.
biblioteka.phpPHPzmienione: 4 linie
<?phptry {$pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - autorzy po nazwisku$authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$authors = [];$databaseError = 'Nie udało się połączyć z bazą. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Biblioteka publiczna</title> <linkrel="stylesheet"href="style.css"></head><body> <header> <h1>Biblioteka w Książkowicach Wielkich</h1> </header> <main> <sectionid="authors"> <h3>Polecamy dzieła autorów:</h3> <ol> </ol> </section> <sectionid="address"> <h3>ul. Czytelnicza 25, Książkowice Wielkie</h3> <p><ahref="mailto:[email protected]">Napisz do nas</a></p> <imgsrc="biblioteka.png"alt="książki"> </section> <sectionid="new-reader"> <h3>Dodaj czytelnika</h3> <formmethod="post"> <div> <labelfor="name">imię: </label> <inputtype="text"id="name"name="name"requiredautocomplete="given-name"> </div> <div> <labelfor="surname">nazwisko: </label> <inputtype="text"id="surname"name="surname"requiredautocomplete="family-name"> </div> <div> <labelfor="code">symbol: </label> <inputtype="number"id="code"name="code"requiredmin="0"max="999999"> </div> <buttontype="submit">DODAJ</button> </form> </section> <sectionid="status"> </section> </main> <footer> <p>Projekt strony: Bartosz Bryniarski</p> </footer></body></html>
R.2.6 W bloku lewym umieszczono jedną listę numerowaną, w kodzie HTML lub skrypcie, zastosowano znaczniki <ol>, <li>
R.5.3 Skrypt 1 wyświetla imiona i nazwiska autorów zwrócone zapytaniem 1
R.5.4 Skrypt 1 generuje rekordy jako elementy listy z wykorzystaniem pętli
ok. 3 min
Polecenie z arkusza
Pobrane zapytaniem imiona i nazwiska autorów wypisywane są jako elementy listy numerowanej (uporządkowanej)
foreach z dwukropkiem i endforeach - pętla wypisuje jeden <li> na autora (R.5.4); numery 1-10 dopisuje przeglądarka, bo elementy stoją w <ol>,
pola po nazwach kolumn ($author['imie']), nie po numerach,
funkcja e() (na górze pliku) przepuszcza każdy tekst z bazy przez htmlspecialchars - znak < albo cudzysłów w danych nie rozbije strony. <?= ... ?> to skrót od <?php echo ... ?>.
Lista z obrazu 2, od Jana Brzechwy do Magdaleny Witkiewicz (R.5.3, R.2.6).
biblioteka.phpPHPzmienione: 8 linii
<?phpfunctione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - autorzy po nazwisku$authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$authors = [];$databaseError = 'Nie udało się połączyć z bazą. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Biblioteka publiczna</title> <linkrel="stylesheet"href="style.css"></head><body> <header> <h1>Biblioteka w Książkowicach Wielkich</h1> </header> <main> <sectionid="authors"> <h3>Polecamy dzieła autorów:</h3> <ol><?phpforeach ($authorsas$author): ?> <li><?= e($author['imie']) ?><?= e($author['nazwisko']) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="address"> <h3>ul. Czytelnicza 25, Książkowice Wielkie</h3> <p><ahref="mailto:[email protected]">Napisz do nas</a></p> <imgsrc="biblioteka.png"alt="książki"> </section> <sectionid="new-reader"> <h3>Dodaj czytelnika</h3> <formmethod="post"> <div> <labelfor="name">imię: </label> <inputtype="text"id="name"name="name"requiredautocomplete="given-name"> </div> <div> <labelfor="surname">nazwisko: </label> <inputtype="text"id="surname"name="surname"requiredautocomplete="family-name"> </div> <div> <labelfor="code">symbol: </label> <inputtype="number"id="code"name="code"requiredmin="0"max="999999"> </div> <buttontype="submit">DODAJ</button> </form> </section> <sectionid="status"> </section> </main> <footer> <p>Projekt strony: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (biblioteka.php)
Krok 37: Skrypt 2: dane z formularza
Kryteria:
R.5.6 Skrypt 2 zawiera instrukcję pobierającą dane z tablicy globalnej odpowiadającej zastosowanej metodzie formularza ($_POST, $_GET, $_REQUEST)
ok. 4 min
Polecenie z arkusza
Skrypt 2: Pobiera dane z formularza
$_SERVER['REQUEST_METHOD'] === 'POST' - czy strona przyszła z wysłanego formularza, czy to zwykłe wejście,
$_POST['name'] ?? '' - wartość z tablicy globalnej $_POST (R.5.6), a gdy formularza nie wysłano - pusty napis, bez ostrzeżenia „Undefined array key”,
trim usuwa spacje z brzegów - sama spacja w polu to też „nic”,
$isValid - imię i nazwisko niepuste, a symbol to od 1 do 6 cyfr (preg_match z wyrażeniem ^[0-9]{1,6}$), bo tyle mieści kolumna kod typu varchar(6). required w formularzu tego nie gwarantuje - żądanie da się wysłać z pominięciem przeglądarki.
Od tego kroku podgląd pokazuje stronę po wysłaniu formularza z danymi Anny Nowakowskiej; na razie nic się nie zmienia.
biblioteka.phpPHPzmienione: 7 linii
<?php// Skrypt 2: dane z formularza$formSent = $_SERVER['REQUEST_METHOD'] === 'POST';$name = trim($_POST['name'] ?? '');$surname = trim($_POST['surname'] ?? '');$code = trim($_POST['code'] ?? '');$isValid = $name !== '' && $surname !== '' && preg_match('/^[0-9]{1,6}$/', $code) === 1;functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - autorzy po nazwisku$authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$authors = [];$databaseError = 'Nie udało się połączyć z bazą. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Biblioteka publiczna</title> <linkrel="stylesheet"href="style.css"></head><body> <header> <h1>Biblioteka w Książkowicach Wielkich</h1> </header> <main> <sectionid="authors"> <h3>Polecamy dzieła autorów:</h3> <ol><?phpforeach ($authorsas$author): ?> <li><?= e($author['imie']) ?><?= e($author['nazwisko']) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="address"> <h3>ul. Czytelnicza 25, Książkowice Wielkie</h3> <p><ahref="mailto:[email protected]">Napisz do nas</a></p> <imgsrc="biblioteka.png"alt="książki"> </section> <sectionid="new-reader"> <h3>Dodaj czytelnika</h3> <formmethod="post"> <div> <labelfor="name">imię: </label> <inputtype="text"id="name"name="name"requiredautocomplete="given-name"> </div> <div> <labelfor="surname">nazwisko: </label> <inputtype="text"id="surname"name="surname"requiredautocomplete="family-name"> </div> <div> <labelfor="code">symbol: </label> <inputtype="number"id="code"name="code"requiredmin="0"max="999999"> </div> <buttontype="submit">DODAJ</button> </form> </section> <sectionid="status"> </section> </main> <footer> <p>Projekt strony: Bartosz Bryniarski</p> </footer></body></html>
R.5.5 Skrypt 2 wyświetla napis: "Czytelnik <imie> <nazwisko> został(a) dodany do bazy danych" (gdzie pola <> są pobrane z formularza)
ok. 2 min
Polecenie z arkusza
Wyświetla napis „Czytelnik <imie><nazwisko> został(a) dodany do bazy danych” (gdzie pola <> są pobrane z formularza)
Zmienna $addedReader - imię i nazwisko, gdy formularz wysłano z poprawnymi danymi, a w przeciwnym razie null. W panelu prawym dolnym akapit z napisem z arkusza, imię i nazwisko przez e() - tekst od użytkownika nie wstrzyknie do strony kodu HTML. Podgląd: „Czytelnik Anna Nowakowska został(a) dodany do bazy danych”, jak na obrazie 2 (R.5.5).
biblioteka.phpPHPzmienione: 4 linie
<?php// Skrypt 2: dane z formularza$formSent = $_SERVER['REQUEST_METHOD'] === 'POST';$name = trim($_POST['name'] ?? '');$surname = trim($_POST['surname'] ?? '');$code = trim($_POST['code'] ?? '');$isValid = $name !== '' && $surname !== '' && preg_match('/^[0-9]{1,6}$/', $code) === 1;$addedReader = $formSent && $isValid ? "$name$surname" : null;functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - autorzy po nazwisku$authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$authors = [];$databaseError = 'Nie udało się połączyć z bazą. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Biblioteka publiczna</title> <linkrel="stylesheet"href="style.css"></head><body> <header> <h1>Biblioteka w Książkowicach Wielkich</h1> </header> <main> <sectionid="authors"> <h3>Polecamy dzieła autorów:</h3> <ol><?phpforeach ($authorsas$author): ?> <li><?= e($author['imie']) ?><?= e($author['nazwisko']) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="address"> <h3>ul. Czytelnicza 25, Książkowice Wielkie</h3> <p><ahref="mailto:[email protected]">Napisz do nas</a></p> <imgsrc="biblioteka.png"alt="książki"> </section> <sectionid="new-reader"> <h3>Dodaj czytelnika</h3> <formmethod="post"> <div> <labelfor="name">imię: </label> <inputtype="text"id="name"name="name"requiredautocomplete="given-name"> </div> <div> <labelfor="surname">nazwisko: </label> <inputtype="text"id="surname"name="surname"requiredautocomplete="family-name"> </div> <div> <labelfor="code">symbol: </label> <inputtype="number"id="code"name="code"requiredmin="0"max="999999"> </div> <buttontype="submit">DODAJ</button> </form> </section> <sectionid="status"><?phpif ($addedReader !== null): ?> <p>Czytelnik <?= e($addedReader) ?> został(a) dodany do bazy danych</p><?phpendif; ?> </section> </main> <footer> <p>Projekt strony: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (biblioteka.php)
Krok 39: Skrypt 2: zapytanie 2 z danymi z formularza
Kryteria:
R.5.7 Skrypt 2 dodaje do tabeli czytelnicy nowy rekord, zawierający dane pobrane z formularza. Należy sprawdzić w phpMyAdmin
ok. 3 min
Polecenie z arkusza
Wysyła do bazy danych zmodyfikowane zapytanie 2 tak, że wstawia do bazy danych dane z formularza
Zapytanie przygotowane: w miejscu danych stoją parametry :name, :surname, :code, a wartości podaje execute([...]). Baza dostaje treść zapytania i dane osobno, więc tekst z formularza nigdy nie stanie się częścią SQL - nazwisko O'Hara z apostrofem zapisuje się poprawnie (sprawdzone), zamiast przerwać zapytanie (R.5.7).
zapytanie wykonuje się tylko dla poprawnych danych (if ($formSent && $isValid)),
$addedReader dostaje imię i nazwisko dopiero po zapisie - napis o dodaniu pojawia się tylko wtedy, gdy czytelnik naprawdę jest w bazie,
$statement = null zwalnia zapytanie przed zamknięciem połączenia.
Sprawdzenie, jak każe klucz: w phpMyAdmin tabela czytelnicy ma nowy wiersz Anna Nowakowska, 145322.
Typowe błędy
Tabela czytelnicy z pliku arkusza ma zestaw znaków latin1 - polskie litery z formularza zapisują się jako „?” (sprawdzone: „Łucja Żak” → „?ucja ?ak”), choć napis na stronie jest poprawny. Na egzaminie nie zmieniaj struktury tabel z arkusza; poza egzaminem naprawia to ALTER TABLE czytelnicy CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;.
biblioteka.phpPHPzmienione: 9 linii
<?php// Skrypt 2: dane z formularza$formSent = $_SERVER['REQUEST_METHOD'] === 'POST';$name = trim($_POST['name'] ?? '');$surname = trim($_POST['surname'] ?? '');$code = trim($_POST['code'] ?? '');$isValid = $name !== '' && $surname !== '' && preg_match('/^[0-9]{1,6}$/', $code) === 1;$addedReader = null;functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - autorzy po nazwisku$authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();// Skrypt 2: zapytanie 2 - nowy czytelnik z formularzaif ($formSent && $isValid) {$statement = $pdo->prepare('INSERT INTO czytelnicy (imie, nazwisko, kod) VALUES (:name, :surname, :code)');$statement->execute(['name' => $name, 'surname' => $surname, 'code' => $code]);$statement = null;$addedReader = "$name$surname"; }$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$authors = [];$databaseError = 'Nie udało się połączyć z bazą. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Biblioteka publiczna</title> <linkrel="stylesheet"href="style.css"></head><body> <header> <h1>Biblioteka w Książkowicach Wielkich</h1> </header> <main> <sectionid="authors"> <h3>Polecamy dzieła autorów:</h3> <ol><?phpforeach ($authorsas$author): ?> <li><?= e($author['imie']) ?><?= e($author['nazwisko']) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="address"> <h3>ul. Czytelnicza 25, Książkowice Wielkie</h3> <p><ahref="mailto:[email protected]">Napisz do nas</a></p> <imgsrc="biblioteka.png"alt="książki"> </section> <sectionid="new-reader"> <h3>Dodaj czytelnika</h3> <formmethod="post"> <div> <labelfor="name">imię: </label> <inputtype="text"id="name"name="name"requiredautocomplete="given-name"> </div> <div> <labelfor="surname">nazwisko: </label> <inputtype="text"id="surname"name="surname"requiredautocomplete="family-name"> </div> <div> <labelfor="code">symbol: </label> <inputtype="number"id="code"name="code"requiredmin="0"max="999999"> </div> <buttontype="submit">DODAJ</button> </form> </section> <sectionid="status"><?phpif ($addedReader !== null): ?> <p>Czytelnik <?= e($addedReader) ?> został(a) dodany do bazy danych</p><?phpendif; ?> </section> </main> <footer> <p>Projekt strony: Bartosz Bryniarski</p> </footer></body></html>
Krok 40: Odświeżenie strony nie dodaje czytelnika drugi raz (PRG)
ok. 4 min
Dodatek spoza arkusza - klucz go nie ocenia. Po wysłaniu formularza strona jest wynikiem żądania POST: odświeżenie (F5) wysyła formularz jeszcze raz i ten sam czytelnik trafia do bazy drugi raz. Wzorzec PRG (Post/Redirect/Get) to usuwa:
po udanym zapisie strona zapamiętuje imię i nazwisko w sesji ($_SESSION, wymaga session_start() na samym początku pliku),
header('Location: biblioteka.php') i exit - przeglądarka dostaje polecenie, żeby wczytać stronę jeszcze raz, tym razem zwykłym żądaniem GET. exit jest konieczne: header tylko ustawia nagłówek odpowiedzi i nie zatrzymuje skryptu,
nowe wejście odczytuje napis z sesji i od razu go usuwa (unset) - pokazuje się raz.
Połączenie jest już zamknięte, zanim padnie przekierowanie, więc $pdo = null dalej jest ostatnią operacją na bazie. Sprawdzone na prawdziwym PHP: wysłanie formularza daje odpowiedź 302, po niej napis o dodaniu Anny Nowakowskiej, a kolejne odświeżenie - stronę bez napisu i bez drugiego wiersza w tabeli czytelnicy.
biblioteka.phpPHPzmienione: 11 linii
<?phpsession_start();// Skrypt 2: dane z formularza$formSent = $_SERVER['REQUEST_METHOD'] === 'POST';$name = trim($_POST['name'] ?? '');$surname = trim($_POST['surname'] ?? '');$code = trim($_POST['code'] ?? '');$isValid = $name !== '' && $surname !== '' && preg_match('/^[0-9]{1,6}$/', $code) === 1;$addedReader = null;functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - autorzy po nazwisku$authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();// Skrypt 2: zapytanie 2 - nowy czytelnik z formularzaif ($formSent && $isValid) {$statement = $pdo->prepare('INSERT INTO czytelnicy (imie, nazwisko, kod) VALUES (:name, :surname, :code)');$statement->execute(['name' => $name, 'surname' => $surname, 'code' => $code]);$statement = null;$addedReader = "$name$surname"; }$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$authors = [];$databaseError = 'Nie udało się połączyć z bazą. Spróbuj odświeżyć stronę za chwilę.';}// PRG: po zapisie przekierowanie na tę samą stronę - odświeżenie nie wyśle formularza drugi razif ($addedReader !== null) {$_SESSION['addedReader'] = $addedReader;header('Location: biblioteka.php');exit;}$addedReader = $_SESSION['addedReader'] ?? null;unset($_SESSION['addedReader']);?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Biblioteka publiczna</title> <linkrel="stylesheet"href="style.css"></head><body> <header> <h1>Biblioteka w Książkowicach Wielkich</h1> </header> <main> <sectionid="authors"> <h3>Polecamy dzieła autorów:</h3> <ol><?phpforeach ($authorsas$author): ?> <li><?= e($author['imie']) ?><?= e($author['nazwisko']) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="address"> <h3>ul. Czytelnicza 25, Książkowice Wielkie</h3> <p><ahref="mailto:[email protected]">Napisz do nas</a></p> <imgsrc="biblioteka.png"alt="książki"> </section> <sectionid="new-reader"> <h3>Dodaj czytelnika</h3> <formmethod="post"> <div> <labelfor="name">imię: </label> <inputtype="text"id="name"name="name"requiredautocomplete="given-name"> </div> <div> <labelfor="surname">nazwisko: </label> <inputtype="text"id="surname"name="surname"requiredautocomplete="family-name"> </div> <div> <labelfor="code">symbol: </label> <inputtype="number"id="code"name="code"requiredmin="0"max="999999"> </div> <buttontype="submit">DODAJ</button> </form> </section> <sectionid="status"><?phpif ($addedReader !== null): ?> <p>Czytelnik <?= e($addedReader) ?> został(a) dodany do bazy danych</p><?phpendif; ?> </section> </main> <footer> <p>Projekt strony: Bartosz Bryniarski</p> </footer></body></html>
Dodatek spoza arkusza - klucz go nie ocenia. Panel prawy dolny odpowiada teraz także w sytuacjach, których obraz 2 nie pokazuje:
niepełne albo złe dane (sama spacja w imieniu, puste nazwisko, symbol „1234567” albo „-5”, wysłane z pominięciem formularza) - „Wpisz imię, nazwisko i symbol (od 1 do 6 cyfr).”, a do bazy nic nie trafia,
awaria bazy (catch) - komunikat z role="alert" (czytnik ekranu przeczyta go od razu), a reszta strony działa dalej.
Sprawdzone na prawdziwym PHP 8.2: cztery błędne zestawy danych i złe hasło do bazy - za każdym razem cała strona, bez ostrzeżeń PHP i bez nowych wierszy w tabeli czytelnicy.
biblioteka.phpPHPzmienione: 5 linii
<?phpsession_start();// Skrypt 2: dane z formularza$formSent = $_SERVER['REQUEST_METHOD'] === 'POST';$name = trim($_POST['name'] ?? '');$surname = trim($_POST['surname'] ?? '');$code = trim($_POST['code'] ?? '');$isValid = $name !== '' && $surname !== '' && preg_match('/^[0-9]{1,6}$/', $code) === 1;$addedReader = null;functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - autorzy po nazwisku$authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();// Skrypt 2: zapytanie 2 - nowy czytelnik z formularzaif ($formSent && $isValid) {$statement = $pdo->prepare('INSERT INTO czytelnicy (imie, nazwisko, kod) VALUES (:name, :surname, :code)');$statement->execute(['name' => $name, 'surname' => $surname, 'code' => $code]);$statement = null;$addedReader = "$name$surname"; }$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$authors = [];$databaseError = 'Nie udało się połączyć z bazą. Spróbuj odświeżyć stronę za chwilę.';}// PRG: po zapisie przekierowanie na tę samą stronę - odświeżenie nie wyśle formularza drugi razif ($addedReader !== null) {$_SESSION['addedReader'] = $addedReader;header('Location: biblioteka.php');exit;}$addedReader = $_SESSION['addedReader'] ?? null;unset($_SESSION['addedReader']);?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Biblioteka publiczna</title> <linkrel="stylesheet"href="style.css"></head><body> <header> <h1>Biblioteka w Książkowicach Wielkich</h1> </header> <main> <sectionid="authors"> <h3>Polecamy dzieła autorów:</h3> <ol><?phpforeach ($authorsas$author): ?> <li><?= e($author['imie']) ?><?= e($author['nazwisko']) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="address"> <h3>ul. Czytelnicza 25, Książkowice Wielkie</h3> <p><ahref="mailto:[email protected]">Napisz do nas</a></p> <imgsrc="biblioteka.png"alt="książki"> </section> <sectionid="new-reader"> <h3>Dodaj czytelnika</h3> <formmethod="post"> <div> <labelfor="name">imię: </label> <inputtype="text"id="name"name="name"requiredautocomplete="given-name"> </div> <div> <labelfor="surname">nazwisko: </label> <inputtype="text"id="surname"name="surname"requiredautocomplete="family-name"> </div> <div> <labelfor="code">symbol: </label> <inputtype="number"id="code"name="code"requiredmin="0"max="999999"> </div> <buttontype="submit">DODAJ</button> </form> </section> <sectionid="status"><?phpif (isset($databaseError)): ?> <prole="alert"><?= e($databaseError) ?></p><?phpelseif ($formSent && !$isValid): ?> <prole="alert">Wpisz imię, nazwisko i symbol (od 1 do 6 cyfr).</p><?phpendif; ?><?phpif ($addedReader !== null): ?> <p>Czytelnik <?= e($addedReader) ?> został(a) dodany do bazy danych</p><?phpendif; ?> </section> </main> <footer> <p>Projekt strony: Bartosz Bryniarski</p> </footer></body></html>
Krok 42 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 42: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 3 min
Obie wersje dają komplet 37 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ
float: left paneli, prawy dolny „opada” pod prawy górny, clear stopki
<main> z flex-flow: column wrap i wysokością 500 px
formularz
<label> wokół pola, <br>
label for, wiersze w <div>, required, autocomplete, max
zapytania 3 i 4
pełne nazwy tabel
aliasy k, kat, w
biblioteka
mysqli (tabela 1 arkusza)
PDO z wyjątkami
gdzie są zapytania
skrypty w liście i w panelu prawym dolnym
jeden blok na górze biblioteka.php
dane z formularza
$_POST wprost do zapytania
$_POST + trim + sprawdzenie symbolu
INSERT
dane wklejone w treść zapytania
parametry :name, :surname, :code
odświeżenie po zapisie
dodaje czytelnika drugi raz
PRG - przekierowanie, napis z sesji
wypisywanie
dane z formularza wprost
e() - htmlspecialchars
W prawdziwym projekcie przy formularzu zapisującym dane dochodzi jeszcze token CSRF - losowy napis z sesji w ukrytym polu, który potwierdza, że formularz wysłano z tej strony, a nie z cudzej. Klucz tego nie wymaga. Dane logowania do bazy trzyma się zwykle w osobnym pliku (np. config.php), ale arkusz każe umieścić skrypt w pliku biblioteka.php.
Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie zapisuje pustych ani podwójnych czytelników i nie wywraca się na apostrofie. Obie biblioteki klucz przyjmuje.
Krok 43 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 43: Plik przeglądarka.txt i nagranie płyty
ok. 5 min
Polecenie z arkusza
Po zakończeniu pracy utwórz w folderze z numerem zdającego plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Nagraj płytę z rezultatami pracy.
W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była strona. W folderze z numerem zdającego: biblioteka.php, biblioteka.png, import.jpg, kwerenda1.jpg - kwerenda4.jpg, kwerendy.txt, przeglądarka.txt i style.css. Nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego.
Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.
To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.
Autor rozwiązania: Bartosz Bryniarski. Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.