INF.03-07-23.06-SGCzerwiec 2023 · 42 kroki · ok. 81 min · 37 kryteriów z klucza
Rozwiązanie krok po kroku: Portal biblioteki (Nowoczesny kod (flex, PDO))
To drugi sposób rozwiązania tego samego arkusza (wydanego też pod numerami 09 i 11). Wynik jest ten sam - baza biblioteka z czterema zapytaniami, grafika w odcieniach szarości i strona biblioteka.php z formularzem dodawania czytelnika, adresem i listą czytelników - i tak samo spełnia każde kryterium klucza. Inna jest droga:
flexbox zamiast float przy układzie paneli: trzy panele stoją w znaczniku <main> ustawionym w rząd, bez clear przy stopce. Szerokości 40%, 20% i 40% zostają na samych panelach - tak ocenia je klucz. float zostaje tylko przy obrazie, bo tam robi to, do czego powstał - tekst oblewa grafikę,
zmienne CSS dla kolorów z arkusza, wiersze formularza w blokach zamiast <br>, pola z required i podpowiedziami autouzupełniania,
PHP na PDO: całą pracę z bazą strona wykonuje na górze pliku biblioteka.php, przed HTML-em - najpierw zapis nowego czytelnika (Skrypt 1), potem lista (Skrypt 2), więc nowa osoba od razu jest na liście,
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, a napis „Dodano czytelnika …” przenosi sesja - odświeżenie nie doda tej samej osoby 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 materialy7, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 na bazie z arkusza po zapytaniu 1 - od Skryptu 1 po wysłaniu formularza z danymi z obrazu 2, Moniki Nowak (obraz nie pokazuje jej symbolu, więc w podglądzie stoi przykładowy 56678).
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 materialy7.7z z pulpitu (hasło @Z@d@nie$s7 - małpy zamiast „a”, dolar zamiast „s”, wielkość liter ma znaczenie) 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 trzech tabel z obrazu 1: czytelnicy (10 osób), autorzy (10) i ksiazki (21 tytułów, pole id_autor wskazuje autora).
Krok 3: Zrzut ekranu dane.jpg
Kryteria:
R.1.1 Wykonano import tabel do bazy danych o nazwie biblioteka , czynność udokumentowano plikiem o nazwie dane w formacie JPEG
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w folderze z numerem zdającego, w formacie JPEG i nazwij dane. 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ą dane - w tym arkuszu zrzut importu nie nazywa się import. Zrzuty zapytań: kwerenda1 - kwerenda4, z widocznym zapytaniem; zapytania 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: nowa czytelniczka Ewelina Romanowska
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 wstawiające do tabeli czytelnicy rekord Ewelina, Romanowska, 56677, wartość klucza głównego jest nadawana automatycznie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO czytelnicy(`imie`, `nazwisko`, `kod`) VALUES ('Ewelina', 'Romanowska', '56677'); (możliwe również bez struktury tabeli lub przez przypisania SET) oraz wynik działania zapytania udokumentowano zrzutem ekranu na którym widoczny jest komunikat o poprawnym wykonaniu zapytania lub tabela z wstawionym wierszem
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wstawiające do tabeli czytelnicy nowy rekord z danymi: Ewelina Romanowska z kodem 56677. Pole klucza głównego nadawane automatycznie
Lista pól bez id - klucz główny ma AUTO_INCREMENT, baza nada numer 11 sama. kod to tekst (varchar(6)), więc też w apostrofach. Komunikat „1 wiersz wstawiony” na zrzucie kwerenda1.jpg. To zapytanie wyśle Skrypt 1 - z parametrami w miejscu danych.
Typowe błędy
Zapytanie wykonane dwa razy doda dwie Eweliny Romanowskie - na liście czytelników będą wtedy dwie.
R.1.4 Zapisano zapytanie 2 wybierające jedynie pola imie i nazwisko dla wszystkich rekordów w tabeli czytelnicy , posortowane rosnąco nazwiskami. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT imie, nazwisko FROM czytelnicy ORDER BY nazwisko; (opcjonalnie ASC) oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym widocznych jest minimum 10 rekordów jedynie pola imie i nazwisko
ok. 2 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie pola imie i nazwisko dla wszystkich rekordów w tabeli czytelnicy, posortowane rosnąco według nazwisk
ORDER BY nazwisko, imie - rosnąco według nazwisk, a przy takich samych nazwiskach według imion: kolejność osób o tym samym nazwisku nie zależy wtedy od przypadku. Wynik: 11 osób (10 z importu i Ewelina Romanowska), od Olgi Domys do Macieja Wysockiego. Zrzut: kwerenda2.jpg. To zapytanie wyśle Skrypt 2.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1INSERT INTO czytelnicy (imie, nazwisko, kod)VALUES ('Ewelina', 'Romanowska', '56677');-- Zapytanie 2SELECT imie, nazwiskoFROM czytelnicyORDER BY nazwisko, imie;
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: książki Sienkiewicza
Kryteria:
R.1.5 Zapisano zapytanie 3 wybierające jedynie pole tytul dla wszystkich utworów, których nazwisko autora to Sienkiewicz. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT tytul FROM ksiazki JOIN autorzy ON ksiazki.id_autor = autorzy.id WHERE nazwisko = 'Sienkiewicz'; (lub INNER JOIN, lub porównanie kluczy po WHERE; warunek id_autor=8 jest błędem) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są dokładnie 3 tytuły: Quo vadis, Potop, Ogniem i mieczem
ok. 2 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie pole tytul dla wszystkich utworów, których nazwisko autora to Sienkiewicz
Aliasy k (ksiazki) i a (autorzy), JOIN autorzy AS a ON a.id = k.id_autor - nazwisko jest w innej tabeli niż tytuł - i warunek na nazwisku. Wynik: Quo vadis, Potop, Ogniem i mieczem. Zrzut: kwerenda3.jpg.
Typowe błędy
Klucz wprost uznaje warunek id_autor = 8 za błąd - arkusz mówi o nazwisku, więc trzeba złączyć tabele.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1INSERT INTO czytelnicy (imie, nazwisko, kod)VALUES ('Ewelina', 'Romanowska', '56677');-- Zapytanie 2SELECT imie, nazwiskoFROM czytelnicyORDER BY nazwisko, imie;-- Zapytanie 3SELECT k.tytulFROM ksiazki AS kJOIN autorzy AS a ON a.id = k.id_autorWHERE a.nazwisko = 'Sienkiewicz';
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: liczba tytułów każdego autora
Kryteria:
R.1.6 Zapisano zapytanie 4 wybierające jedynie pole nazwisko dla wszystkich rekordów w tabeli autorzy oraz obliczające liczbę tytułów danego autora znajdujących się w bazie. Nazwa kolumny (alias) obliczającej liczbę tytułów to ile_tytulow. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwisko, COUNT(tytul) AS ile_tytulow FROM autorzy JOIN ksiazki ON ksiazki.id_autor = autorzy.id GROUP BY nazwisko; (możliwe również porównanie kluczy po WHERE, poprawne są LEFT JOIN, RIGHT JOIN, INNER JOIN) oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym widoczne są jedynie nazwiska autorów i pole ile_tytulow: Brzechwa 2, Chmielewska 2, Fredro 2, Gombrowicz 3, Kasprowicz 2, Kruczkowski 2, Sienkiewicz 3, Tuwim 3, Witkiewicz 2 i opcjonalnie Korczak 0
ok. 3 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie pole nazwisko dla wszystkich rekordów w tabeli autorzy oraz obliczające liczbę tytułów danego autora znajdujących się w bazie biblioteka. Nazwa kolumny (alias) dla zliczania tytułów: ile_tytulow
LEFT JOIN - złączenie lewostronne zostawia też autora bez książki („wszystkie rekordy w tabeli autorzy”): Janusz Korczak dostaje 0,
COUNT(k.id) - liczy książki, a nie wiersze: COUNT(*) policzyłby Korczakowi 1 (pusty wiersz złączenia),
GROUP BY a.id, a.nazwisko - grupa na autora po kluczu głównym; dwóch autorów o tym samym nazwisku nie zleje się w jeden wiersz,
ORDER BY a.nazwisko - kolejność alfabetyczna jak w kluczu.
Wynik: 10 wierszy od Brzechwy (2) do Witkiewicz (2), z Korczakiem 0 (klucz dopuszcza go opcjonalnie). Zrzut: kwerenda4.jpg.
kwerendy.txtTekstzmienione: 7 linii
-- Zapytanie 1INSERT INTO czytelnicy (imie, nazwisko, kod)VALUES ('Ewelina', 'Romanowska', '56677');-- Zapytanie 2SELECT imie, nazwiskoFROM czytelnicyORDER BY nazwisko, imie;-- Zapytanie 3SELECT k.tytulFROM ksiazki AS kJOIN autorzy AS a ON a.id = k.id_autorWHERE a.nazwisko = 'Sienkiewicz';-- Zapytanie 4SELECT a.nazwisko, COUNT(k.id) AS ile_tytulowFROM autorzy AS aLEFT JOIN ksiazki AS k ON k.id_autor = a.idGROUP BY a.id, a.nazwiskoORDER BY a.nazwisko;
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)
R.2.1 Grafikę biblioteka.png zapisano w odcieniach szarości oraz przeskalowano z zachowaniem proporcji do szerokości 100 px, odpowiadająca temu wysokość to 157 px (± 5 px)
ok. 3 min
Polecenie z arkusza
Grafikę biblioteka.png przeskalować z zachowaniem proporcji tak, aby szerokość wynosiła dokładnie 100 px oraz zapisać w odcieniach szarości.
Grafika jest taka sama jak w wersji podstawowej. W GIMP-ie Obraz → Skaluj obraz, łańcuch proporcji zamknięty, szerokość 100 - wysokość wyjdzie 157 px (235 × 100 / 150). Potem Obraz → Tryb → Odcienie szarości i Nadpisz biblioteka.png.
Typowe błędy
Odcienie szarości to zmiana trybu obrazu, a nie filtr odbarwiający - po filtrze plik dalej jest kolorowy (RGB), tylko szary w wyglądzie.
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/....
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. 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 #).
R.2.4 Wygląd strony zdefiniowano dzieląc ją na baner, panele lewy, środkowy, prawy 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 prawy 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 trzy panele - trzy <section> we wspólnym <main>, który w CSS ustawi je w rząd (R.2.4). Identyfikatory mówią, co jest w panelu:
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. Skrypt 1 sprawdzi symbol jeszcze raz, bo żądanie da się wysłać z pominięciem formularza.
Krok 20: Panel środkowy: obraz i adres ze spacją nierozdzielającą
Kryteria: ,
R.2.8 W bloku środkowym umieszczono obraz z pliku biblioteka.png z tekstem alternatywnym "biblioteka"
R.2.9 Zastosowano jedynie znak specjalny w danych adresowych: Czytelników 15 (bez zwykłych spacji)
ok. 2 min
Polecenie z arkusza
Zawartość bloku środkowego: Obraz biblioteka.png z tekstem alternatywnym „biblioteka”. Nagłówek szóstego stopnia o treści: „ul. Czytelników 15; Książkowice Małe”, nazwa ulicy i numer lokalu połączone nierozdzielającą spacją
Obraz z tekstem alternatywnym (R.2.8) stoi w kodzie przed tekstem - tekst opłynie go dopiero wtedy, gdy obraz dostanie w CSS float. W <h6> encja zamiast spacji między „Czytelników” a „15”: przeglądarka nie złamie tam wiersza, więc numer nie zostanie sam w następnej linii (R.2.9).
Typowe błędy
Klucz wymaga encji tylko w „Czytelników 15” i bez zwykłej spacji obok niej.
R.2.5 Zapisano w banerze nagłówek h1 "Biblioteka w Książkowicach Małych", w panelu środkowym h6 z adresem biblioteki, w panelach lewym i prawym przynajmniej jeden h4: "Dodaj czytelnika” lub "Nasi czytelnicy:” oraz zapisano przynajmniej jeden paragraf
R.3.5 Napis "Czy masz jakieś uwagi?" jest odsyłaczem pocztowym do adresu [email protected], w definicji zastosowano "mailto:"
ok. 1 min
Polecenie z arkusza
Paragraf (akapit) o treści: „Czy masz jakieś uwagi?”, który jest odsyłaczem pocztowym do adresu [email protected]
Akapit z odnośnikiem mailto: (R.3.5). Z <h1>, <h6> i <h4> to komplet do R.2.5.
Kroki 24-32 · po tym etapie masz 29 z 37 kryteriów (pkt)
Krok 24: Plik style.css: zmienne i krój Tahoma
Kryteria:
R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (*) krój czcionki Tahoma
ok. 2 min
Polecenie z arkusza
Domyślne formatowanie wszystkich selektorów : krój czcionki Tahoma
Cztery kolory z arkusza w zmiennych - nazwy kolorów CSS (SaddleBrown, Tan, Wheat, Sienna) pisze się bez # i bez cudzysłowu. Selektor * z Tahomą 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. 2 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
display: flex na <main> ustawia trzy panele w rzędzie, a baner i stopka stoją nad i pod nim same z siebie - bez float paneli i bez clear stopki (R.3.1).
Dlaczego nie siatka? W siatce procent szerokości elementu liczy się od jego kolumny: panel z width: 40% w kolumnie szerokiej na 40% strony zająłby 16% strony. Szerokości trzeba by przenieść do grid-template-columns, a klucz z tego arkusza ocenia je na panelach. Flex liczy procenty od pojemnika.
Typowe błędy
Margines albo ramka dodana panelom przekracza 100% szerokości - flex ściśnie wtedy panele i żaden nie będzie miał dokładnie 40% ani 20%.
Krok 29: Obraz: margines prawy i oblewanie z lewej
Kryteria:
R.4.8 Ustawiono dla obrazu opływanie tekstem z lewej strony (np. float: right;)
ok. 1 min
Polecenie z arkusza
Dla selektora obrazu: margines zewnętrzny prawy 20 px, oblewanie z lewej strony zgodnie z Obrazem 2
„Oblewanie z lewej strony” - tekst opływa obraz od lewej, czyli obraz dosuwa się do prawej krawędzi panelu: float: right (R.4.8). Tu float zostaje, bo robi dokładnie to, do czego powstał - oblewanie obrazu tekstem; układ bloków strony robi flex. margin-right: 20px odsuwa obraz od krawędzi.
Typowe błędy
float: left daje odwrotny układ (obraz z lewej, tekst z prawej) - klucz wymaga oblewania z lewej, np. float: right.
R.4.7 Ustawiono marginesy wewnętrzne (padding) 3px dla banera i stopki oraz marginesy zewnętrzne (margin) 5px dla selektora input oraz margines zewnętrzny prawy (np. margin- right) 20px dla selektora img
ok. 1 min
Polecenie z arkusza
Dla selektora pola edycyjnego: marginesy zewnętrzne 5 px
Selektor znacznika input. Z padding banera i stopki i margin-right obrazu to komplet odstępów z arkusza (R.4.7).
Kroki 33-40 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 33: 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 zamknięcie połączenia z serwerem bazy danych
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. To porządkuje kolejność: najpierw zapis nowego czytelnika, potem odczyt listy - więc dodana osoba od razu jest na liście, jak na obrazie 2. Ten układ przyda się też do przekierowania po zapisie, które da się wysłać 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.
R.5.3 Skrypt 1 wyświetla napis: "Dodano czytelnika <imie> <nazwisko>”, gdzie pola <> pobrano z formularza
ok. 2 min
Polecenie z arkusza
Wyświetla napis „Dodano czytelnika <imie><nazwisko>”, gdzie pola <> pobrano z formularza
Zmienna $addedReader - imię i nazwisko, gdy formularz wysłano z poprawnymi danymi, a w przeciwnym razie null. Pod formularzem akapit „Dodano czytelnika Monika Nowak”, jak na obrazie 2 (R.5.3). Imię i nazwisko przechodzą przez funkcję e() (htmlspecialchars) - tekst od użytkownika nie wstrzyknie do strony kodu HTML.
Krok 36: Skrypt 1: zapytanie 1 z danymi z formularza
Kryteria:
R.5.4 Skrypt 1 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 1 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.4).
zapytanie wykonuje się tylko dla poprawnych danych (if ($formSent && $isValid)),
$addedReader dostaje imię i nazwisko dopiero po zapisie - napis 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 Monika Nowak, 56678.
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;.
Zapytanie bez danych od użytkownika, więc wystarczy query; fetchAll() zwraca od razu wszystkie wiersze. Stoi po zapisie ze Skryptu 1, więc lista zawiera już nowego czytelnika. W catch lista dostaje wartość pustą, żeby HTML niżej nie trafił na niezdefiniowaną zmienną. Na stronie jeszcze nic nie widać.
Krok 39: 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 na liście są dwie Moniki Nowak. Wzorzec PRG (Post/Redirect/Get) to usuwa, a napis „Dodano czytelnika …” z arkusza zostaje:
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 wczytuje stronę jeszcze raz, zwykłym żądaniem GET. exit jest konieczne: header tylko ustawia nagłówek odpowiedzi i nie zatrzymuje skryptu,
nowe wejście odczytuje napis z sesji, od razu go usuwa (unset) i pokazuje raz - nad listą, w której nowa osoba już jest.
Połączenie jest 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 i Monika Nowak na liście, a kolejne odświeżenie - lista bez drugiej Moniki i bez napisu.
biblioteka.phpPHPzmienione: 11 linii
<?phpsession_start();// Skrypt 1: 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 - 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"; }// Skrypt 2: zapytanie 2 - czytelnicy po nazwisku$readers = $pdo->query('SELECT imie, nazwisko FROM czytelnicy ORDER BY nazwisko, imie')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$readers = [];$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</title> <linkrel="stylesheet"href="style.css"></head><body> <header> <h1>Biblioteka w Książkowicach Małych</h1> </header> <main> <sectionid="new-reader"> <h4>Dodaj czytelnika</h4> <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">AKCEPTUJ</button> </form><?phpif ($addedReader !== null): ?> <p>Dodano czytelnika <?= e($addedReader) ?></p><?phpendif; ?> </section> <sectionid="contact"> <imgsrc="biblioteka.png"alt="biblioteka"> <h6>ul. Czytelników 15; Książkowice Małe</h6> <p><ahref="mailto:[email protected]">Czy masz jakieś uwagi?</a></p> </section> <sectionid="readers"> <h4>Nasi czytelnicy:</h4> <ol><?phpforeach ($readersas$reader): ?> <li><?= e($reader['imie']) ?><?= e($reader['nazwisko']) ?></li><?phpendforeach; ?> </ol> </section> </main> <footer> <p>Projekt witryny: Bartosz Bryniarski</p> </footer></body></html>
Dodatek spoza arkusza - klucz go nie ocenia. Pod formularzem pojawia się komunikat 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 1: 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 - 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"; }// Skrypt 2: zapytanie 2 - czytelnicy po nazwisku$readers = $pdo->query('SELECT imie, nazwisko FROM czytelnicy ORDER BY nazwisko, imie')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$readers = [];$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</title> <linkrel="stylesheet"href="style.css"></head><body> <header> <h1>Biblioteka w Książkowicach Małych</h1> </header> <main> <sectionid="new-reader"> <h4>Dodaj czytelnika</h4> <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">AKCEPTUJ</button> </form><?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>Dodano czytelnika <?= e($addedReader) ?></p><?phpendif; ?> </section> <sectionid="contact"> <imgsrc="biblioteka.png"alt="biblioteka"> <h6>ul. Czytelników 15; Książkowice Małe</h6> <p><ahref="mailto:[email protected]">Czy masz jakieś uwagi?</a></p> </section> <sectionid="readers"> <h4>Nasi czytelnicy:</h4> <ol><?phpforeach ($readersas$reader): ?> <li><?= e($reader['imie']) ?><?= e($reader['nazwisko']) ?></li><?phpendforeach; ?> </ol> </section> </main> <footer> <p>Projekt witryny: Bartosz Bryniarski</p> </footer></body></html>
Krok 41 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 41: 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 paneli
float: left i clear stopki
<main> z display: flex (obraz dalej z float: right)
formularz
<label> wokół pola, <br>
label for, wiersze w <div>, required, autocomplete, max
zapytania
pełne nazwy tabel, GROUP BY nazwisko
aliasy, ORDER BY nazwisko, imie, GROUP BY a.id, a.nazwisko
biblioteka
mysqli (tabela 1 arkusza)
PDO z wyjątkami
gdzie są zapytania
skrypt pod formularzem i w liście
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 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. 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 42 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 42: 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, dane.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.