INF.03-01-26.01-SGStyczeń 2026 · 27 kroków · ok. 65 min · 34 kryteria z klucza
Rozwiązanie krok po kroku: Portal konfiguracji samochodów (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza samochody z czterema zapytaniami i strona index.php z tabelą aut, tabelą konfiguracji ze scalonymi komórkami i sekcją kontaktu - i tak samo spełnia każde kryterium klucza. Inna jest droga:
siatka CSS w bloku głównym: trzy kolumny po 33,3% w grid-template-columns (klucz R.4.4 przyjmuje je wprost) zamiast float: left i clear: both,
PHP na PDO: oba zapytania na górze pliku, połączenie zamknięte, zanim zacznie się HTML, a tabele wypisywane pętlą foreach w szablonie,
bezpieczeństwo: htmlspecialchars na wszystkim, co trafia z bazy na stronę, try/catch zamiast białej strony przy awarii bazy, jawna zamiana cen z napisów na liczby,
funkcja carRows() zamiast dwóch kopii kodu wypisującego wylosowane auto,
zapytania z aliasami i złączeniem JOIN ... ON.
Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafiki pochodzą z prawdziwego archiwum pliki1, zapytania zostały wykonane na tej bazie w MariaDB z XAMPP-a, a podgląd każdej strony PHP to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2. Zapytanie 4 losuje, więc w podglądzie stoją dwa auta z ilustracji 2 (BM beta za 94000 i za 77000).
Jak liczą się punkty. Klucz ma 34 kryteria (R.1.1 - R.5.8), każde za 1 punkt. Próg zdania to 75%, czyli 26 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 34 kryteriów (pkt)
Krok 1: Baza danych samochody
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie samochody, z zestawem polskich znaków (np. utf8_polish_ci).
Archiwum pliki1.7z rozpakuj hasłem S@mocHody^ (z małpą zamiast „a” i daszkiem na końcu): baza.sql i trzy grafiki. W phpMyAdmin Nowa → samochody, porównywanie utf8_polish_ci, albo:
CREATE DATABASE samochody CHARACTER SET utf8 COLLATE utf8_polish_ci;
Typowe błędy
Nazwa bazy musi być dokładnie samochody - skrypt łączy się z bazą po nazwie (R.5.1).
Krok 2: Import tabel z pliku baza.sql
ok. 2 min
Polecenie z arkusza
Z rozpakowanego archiwum zaimportuj tabele z pliku baza.sql do utworzonej bazy.
Import → baza.sql. Dwie tabele: pojazdy (62 auta) i kolory (8 kolorów z dopłatą - metaliki kosztują 1000 zł więcej); pojazdy.kolor to klucz obcy wskazujący kolory.id.
Krok 3: Zrzut ekranu import.jpg
Kryteria:
R.1.1 Wykonany import tabeli do bazy danych samochody - plik import.jpg
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie JPEG pod nazwą 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ń. Uwaga na formaty: import w JPEG (import.jpg), zrzuty zapytań w PNG.
Typowe błędy
Wykadrowany zrzut albo zrzut importu zapisany jako PNG to strata R.1.1.
Krok 4: Zapytanie 1: 15 najdroższych aut marki BM
Kryteria: ,
R.1.2 W pliku kwerendy.txt zapisane co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Wykonane zapytanie 1 wybierające pierwsze 15 rekordów, jedynie pola: marka, model, cena dla pojazdów, których marka to BM posortowane malejąco według ceny. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT marka, model, cena FROM pojazdy WHERE marka = "BM" ORDER BY cena DESC LIMIT 15; oraz na zrzucie jest dokładnie 15 rekordów zawierających jedynie pola marka, model, cena. Ceny od 94000 do 84000
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wybierające pierwsze 15 rekordów, jedynie pola: marka, model, cena dla pojazdów, których marka to BM posortowane malejąco według ceny.
Klauzule zawsze w kolejności SELECT → FROM → WHERE → ORDER BY → LIMIT. LIMIT 15 działa po sortowaniu, więc daje 15 najdroższych. Wynik: ceny od 94000 do 84000. Zapytania zapisuj w kwerendy.txt, zrzut jako kw1.png.
Typowe błędy
LIMIT przed ORDER BY to błąd składni, a bez DESC wychodzi 15 najtańszych.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT marka, model, cenaFROM pojazdyWHERE marka = 'BM'ORDER BY cena DESCLIMIT 15;
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: średnia i najwyższa cena modelu meta
Kryteria:
R.1.4 Wykonane zapytanie 2 liczące średnią cenę pojazdów oraz wybierające najdroższy pojazd dla modelu „meta”. Nazwy kolumn (aliasy) : „Średnia cena”, „Maksymalna cena”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT AVG(cena) AS "Średnia cena", MAX(cena) AS "Maksymalna cena" FROM pojazdy WHERE model = "meta"; (AS nieobowiązkowe, w aliasie dopuszczalne literówki) oraz na zrzucie widoczne są wartości: 92071.4286 oraz 98000 (dowolne zaokrąglenie)
ok. 2 min
Polecenie z arkusza
Zapytanie 2: liczące średnią cenę pojazdów oraz wybierające najdroższy pojazd dla modelu „meta”. Nazwy kolumn (aliasy) odpowiednio: „Średnia cena”, „Maksymalna cena”.
AVG i MAX liczone z wierszy modelu „meta”. Aliasy ze spacją i polską literą w odwrotnych apostrofach - w MySQL i MariaDB to znak dla nazw (cudzysłów z przykładu w kluczu też zadziała). Wynik: jeden wiersz - 92071.4286 i 98000. Zrzut: kw2.png.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT marka, model, cenaFROM pojazdyWHERE marka = 'BM'ORDER BY cena DESCLIMIT 15;-- Zapytanie 2SELECT AVG(cena) AS `Średnia cena`, MAX(cena) AS `Maksymalna cena`FROM pojazdyWHERE model = 'meta';
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: auta modelu alfa z kolorem i dopłatą
Kryteria:
R.1.5 Wykonane zapytanie 3 wybierające z tabeli pojazdy jedynie pola: marka, model, cena oraz odpowiadające im z tabeli kolory pola: nazwa, doplata dla modelu „alfa”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT marka, model, cena, nazwa, doplata FROM pojazdy INNER JOIN kolory ON kolor = kolory.id WHERE model = "alfa"; (możliwe też samo JOIN lub porównanie kluczy po WHERE) oraz na zrzucie widocznych jest dokładnie 6 wierszy, nazwy kolorów: zielony, czarny, czarny, zielony metalik, czerwony metalik, czarny metalik
ok. 3 min
Polecenie z arkusza
Zapytanie 3: wybierające z tabeli pojazdy jedynie pola: marka, model, cena oraz odpowiadające im z tabeli kolory pola: nazwa, doplata dla modelu „alfa”. Należy posłużyć się relacją.
Aliasy p (pojazdy) i k (kolory): przy każdej kolumnie widać, z której tabeli pochodzi, a ON k.id = p.kolor pokazuje parę kluczy relacji. Wynik: 6 wierszy z kolorami zielony, czarny, czarny, zielony metalik, czerwony metalik, czarny metalik (bez ORDER BY kolejność wierszy zależy od serwera). Zrzut: kw3.png. To zapytanie wyśle skrypt 1.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT marka, model, cenaFROM pojazdyWHERE marka = 'BM'ORDER BY cena DESCLIMIT 15;-- Zapytanie 2SELECT AVG(cena) AS `Średnia cena`, MAX(cena) AS `Maksymalna cena`FROM pojazdyWHERE model = 'meta';-- Zapytanie 3SELECT p.marka, p.model, p.cena, k.nazwa, k.doplataFROM pojazdy AS pJOIN kolory AS k ON k.id = p.kolorWHERE p.model = 'alfa';
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: dwa losowe auta
Kryteria:
R.1.6 Wykonane zapytanie 4 losujące dwa dowolne rekordy z tabeli pojazdy i wybierające jedynie markę, model i cenę. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT marka, model, cena FROM pojazdy ORDER BY RAND() LIMIT 2; oraz na zrzucie są dokładnie 2 losowe wiersze z polami marka, model, cena
ok. 2 min
Polecenie z arkusza
Zapytanie 4: losujące dwa dowolne rekordy z tabeli pojazdy i wybierające jedynie markę, model i cenę.
ORDER BY RAND() sortuje wiersze według liczby losowej, LIMIT 2 zostawia dwa pierwsze - przy każdym uruchomieniu inne. Na 62 wierszach to nic nie kosztuje; przy milionach wierszy serwer musiałby wylosować liczbę dla każdego z nich i całość posortować, więc duże serwisy losują inaczej. Zrzut kw4.png: dokładnie 2 wiersze.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT marka, model, cenaFROM pojazdyWHERE marka = 'BM'ORDER BY cena DESCLIMIT 15;-- Zapytanie 2SELECT AVG(cena) AS `Średnia cena`, MAX(cena) AS `Maksymalna cena`FROM pojazdyWHERE model = 'meta';-- Zapytanie 3SELECT p.marka, p.model, p.cena, k.nazwa, k.doplataFROM pojazdy AS pJOIN kolory AS k ON k.id = p.kolorWHERE p.model = 'alfa';-- Zapytanie 4SELECT marka, model, cenaFROM pojazdyORDER BY RAND()LIMIT 2;
Podgląd strony po tym kroku
Etap 2: Strona HTML
Kroki 8-12 · po tym etapie masz 14 z 34 kryteriów (pkt)
Krok 8: Plik index.php: HTML5, język, kodowanie
Kryteria:
R.2.1 W pliku index.php znajduje się deklaracja HTML5 <!DOCTYPE HTML> i deklaracja języka zawartości witryny: polski np. <html lang="pl">
ok. 2 min
Polecenie z arkusza
Składa się ze strony o nazwie index.php. Zapisana w języku HTML5. Zadeklarowany polski język zawartości witryny. Jawnie zastosowany właściwy standard kodowania polskich znaków.
Grafiki a1.jpg, a2.jpg, a3.png z archiwum skopiuj do folderu zdającego. Plik index.php: <!DOCTYPE html>, <html lang="pl"> (R.2.1) i <meta charset="UTF-8"> jako pierwszy element <head>. Rozszerzenie .php, bo strona będzie pobierać dane z bazy - otwierasz ją przez http://localhost/....
R.2.3 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML5: header, nav, main->section+section+section, footer (dopuszcza się zastosowanie bloków pomocniczych)
ok. 3 min
Polecenie z arkusza
Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 3. Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści „Serwis konfiguracji samochodów”. Zawartość bloku nawigacyjnego: trzy nagłówki drugiego stopnia o treści: „Samochody”, „Konfigurator”, „Kontakt”. Zawartość bloku głównego: blok sekcji lewej, środkowej i prawej.
Struktura z klucza R.2.3: header, nav, main z trzema section, footer. W nagłówku <h1>, w nawigacji trzy <h2> - na stronie staną obok siebie po stylach.
Sekcje dostają identyfikatory od treści, nie od położenia: cars (tabela aut), configurator, contact. Nazwy left/right przestałyby pasować, gdyby na telefonie sekcje stanęły jedna pod drugą; nazwy od treści pasują zawsze. Część stylów dotyczy „jedynie” jednej sekcji, więc CSS musi je rozróżniać.
R.2.7 Tabela w bloku sekcji środkowej ma w pierwszym wierszu scalone kolumny 1 i 2; w co najmniej jednym wierszu z obrazem są scalone wszystkie kolumny. W co najmniej jednej komórce z ceną są scalone dwa wiersze
R.2.8 Pierwszy wiersz tabeli z bloku sekcji środkowej zawiera komórki nagłówkowe „Konfiguracja”, „Cena”, zastosowane znaczniki <tr> i <th>
ok. 3 min
Polecenie z arkusza
Zawartość bloku sekcji lewej: tabela z czterema kolumnami, wypełniona skryptem 1. Zawartość bloku sekcji środkowej: Tabela z trzema kolumnami i siedmioma wierszami, niektóre komórki są scalone zgodnie z ilustracją 2. W pierwszym wierszu tabeli komórki nagłówkowe: „Konfiguracja”, „Cena”. W drugim wierszu tabeli obraz a1.jpg z tekstem alternatywnym „Konfiguracja 1”. W piątym wierszu tabeli obraz a2.jpg z tekstem alternatywnym „Konfiguracja 2”.
w sekcji aut pusta tabela - wiersze wypisze skrypt 1,
w sekcji konfiguratora tabela o trzech kolumnach: pierwszy wiersz to komórki nagłówkowe <th>, z których „Konfiguracja” zajmuje dwie kolumny - colspan="2" (R.2.8),
wiersze z obrazami mają jedną komórkę na całą szerokość: colspan="3" (R.2.7).
Tabela ma na razie trzy wiersze; wiersze 3-4 i 6-7 dopisze skrypt 2 - przed i po obrazie a2.jpg.
R.2.4 W bloku nagłówkowym znajduje się <h1>: „Serwis konfiguracji samochodów”, w bloku nawigacyjnym <h2>: „Samochody”, „Konfigurator” i „Kontakt”, w bloku sekcji prawej <h3> „111 222 444” oraz w stopce <p> z numerem zdającego
R.2.5 Na stronie umieszczono grafiki z tekstem alternatywnym: a1.jpg "Konfiguracja 1", a2.jpg "Konfiguracja 2" oraz a3.png "Samochód"
ok. 2 min
Polecenie z arkusza
Zawartość bloku sekcji prawej: Nagłówek trzeciego stopnia o treści „111 222 444”. Obraz a3.png z tekstem alternatywnym „Samochód”. Zawartość stopki: paragraf o treści: „Stronę wykonał: ”, dalej wstawiony numer zdającego.
<h3> z telefonem i kabriolet - komplet trzech obrazów z tekstem alternatywnym (R.2.5). W stopce akapit: na egzaminie po „Stronę wykonał:” wpisujesz swój numer zdającego (PESEL), tu stoi autor rozwiązania. Komplet nagłówków i stopki to R.2.4.
Wspólne dla bloków nagłówkowego, nawigacyjnego i stopki: biały kolor czcionki, marginesy wewnętrzne 2 px. Dodatkowo dla bloków nagłówkowego i stopki: kolor tła DarkGreen oraz dla bloku nawigacyjnego kolor tła ForestGreen.
Wspólna reguła trzech bloków i dwie reguły kolorów tła - każdy selektor w osobnej linii, więc dopisanie czwartego bloku to jedna linia. Biały tekst (R.4.3), marginesy wewnętrzne 2 px.
Krok 15: Sekcje obok siebie: siatka w bloku głównym
Kryteria: , , ,
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 3 w arkuszu egzaminacyjnym (prawidłowo zastosowano właściwości CSS decydujące o układzie bloków na stronie, np. float i clear albo display flex albo grid albo position)
R.4.2 kolor tła: blok nagłówkowy, stopka - DarkGreen, blok nawigacyjny - ForestGreen, bloki wszystkich sekcji - LightGreen
R.4.5 paski przewijania: wszystkie bloki sekcji - auto (overflow)
Dla wszystkich bloków sekcji: kolor tła LightGreen, szerokość 33.3%, wysokość 550 px, jedynie górny margines wewnętrzny 10 px, paski przewijania widoczne jedynie w przypadku przepełnienia bloku.
Blok główny staje się siatką o trzech kolumnach: grid-template-columns: repeat(3, 33.3%) to skrót od 33.3% 33.3% 33.3%, a klucz R.4.4 przyjmuje szerokość sekcji zapisaną właśnie tak („width lub grid-template-columns lub flex”). Sekcje wpadają do kolumn po kolei - bez float w sekcjach i bez clear w stopce, bo siatka nie wyjmuje elementów z przepływu: stopka i tak stoi pod blokiem głównym (R.3.1).
Reguła section: wysokość 550 px, padding-top - jedynie górny margines wewnętrzny (poziomy padding w sekcji nie rozepchnąłby siatki, ale zmieniłby szerokość treści), overflow: auto - pasek tylko w sekcji, której treść się nie mieści (środkowa z dwoma zdjęciami, ilustracja 2), i tło LightGreen. Kolory tła trzech rodzajów bloków to R.4.2, przewijanie - R.4.5, marginesy wewnętrzne - R.4.6.
R.4.8 styl dla nagłówka drugiego stopnia: właściwość display: inline-block
ok. 1 min
Polecenie z arkusza
Dla selektora nagłówka drugiego stopnia: szerokość 33%, sposób wyświetlania elementu liniowo-blokowy.
Tu nie ma wyboru techniki: klucz R.4.8 ocenia wprost display: inline-block na selektorze h2, więc siatka na <nav> dałaby ten sam wygląd, ale nie ten punkt. Nagłówek liniowo-blokowy stoi w linii jak tekst, a przyjmuje szerokość - trzy po 33% mieszczą się obok siebie.
Krok 17: Tabele: obramowanie, szerokość, połączone linie
Kryteria: ,
R.4.4 wysokość: wszystkie bloki sekcji - 550px szerokość: wszystkie bloki sekcji - 33.3% (width lub grid-template-columns lub flex) , selektor table - 90%, selektor h2 - 33%
R.4.7 obramowanie: dla tabeli, selektorów td i th - 1px solid DarkGreen obramowanie połączone (collapse) - tabela
ok. 2 min
Polecenie z arkusza
Wspólne dla selektora tabeli, komórki tabeli i komórki nagłówkowej: obramowanie linią ciągłą o szerokości 1 px i kolorze DarkGreen. Dodatkowo dla selektora tabeli: szerokość 90%, linie obramowania połączone.
Selektory znaczników table, td, th (uwaga arkusza). border-collapse: collapse łączy ramki sąsiednich komórek w jedną linię (R.4.7). Szerokości: sekcje 33,3%, wysokość 550 px, tabele 90% i nagłówki h2 33% - komplet R.4.4.
Krok 18: Obrazy konfiguratora i wiersz aut pod kursorem
Kryteria: ,
R.4.9 styl jedynie dla obrazów z bloku sekcji środkowej: szerokość - 95%, zaokrąglenie rogów - 8px (border-radius)
R.4.10 styl jedynie dla wierszy tabeli z bloku lewego: gdy kursor znajdzie się na wierszu jego kolor tła zmienia się na ForestGreen. Styl nie działa na tabeli z bloku sekcji środkowej
ok. 2 min
Polecenie z arkusza
Jedynie dla obrazów z bloku środkowego: szerokość 95%, zaokrąglenie rogów 8 px. Jedynie dla wierszy tabeli z bloku lewego: gdy kursor znajdzie się na wierszu tabeli jego kolor tła zmienia się na ForestGreen (jak na ilustracji 2).
Selektory potomka ograniczone do jednej sekcji: #configurator img nie rusza kabrioletu w sekcji kontaktu (R.4.9), a #cars tr:hover nie podświetla tabeli konfiguratora (R.4.10 sprawdza też to). 95% liczy się od komórki tabeli, więc zdjęcia mieszczą się w kolumnie.
Kroki 19-25 · po tym etapie masz 34 z 34 kryteriów (pkt)
Krok 19: Połączenie PDO na górze pliku
Kryteria: , ,
R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy samochody
R.5.2 Ostatnią operacją na bazie jest jej zamknięcie
R.5.8 W skryptach zastosowana przynajmniej jedna zmienna oraz nazwy wszystkich zmiennych są w języku polskim lub angielskim i odzwierciedlają cel zastosowania
ok. 4 min
Polecenie z arkusza
Napisane w języku PHP. Należy stosować znaczące nazewnictwo zmiennych i funkcji w języku polskim lub angielskim. Łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie samochody.
Cała praca z bazą stoi przed<!DOCTYPE html>: najpierw dane, potem HTML, który je tylko wypisuje. Połączenie przez PDO (klucz R.5 przyjmuje MySQLi i PDO):
napis DSN 'mysql:host=localhost;dbname=samochody;charset=utf8mb4' - serwer, baza i kodowanie; 'root', '' - użytkownik i puste hasło (R.5.1),
ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze jako tablice z nazwami kolumn,
$pdo = null zamyka połączenie (PDO nie ma osobnej funkcji „zamknij”) - zapytania trafią między te dwie linie, więc zamknięcie zostanie ostatnią operacją na bazie (R.5.2),
try / catch (PDOException $exception) - gdy serwer bazy nie odpowiada, szczegóły idą do dziennika (error_log), a strona dostaje ogólny komunikat w $dbError,
funkcja e() to krótka nazwa htmlspecialchars(..., ENT_QUOTES); będzie przy każdej wartości z bazy.
Zmienne $cars, $randomCars, $dbError mają wartości początkowe, żeby szablon działał także po błędzie. Nazwy po angielsku, mówią, co przechowują (R.5.8). Od tego kroku podgląd to wynik prawdziwego uruchomienia.
R.5.3 Skrypty zawierają instrukcję wysyłającą do bazy danych zapytanie 3 lub zapytanie 4
ok. 2 min
Polecenie z arkusza
Skrypt 1: Wysyła do bazy danych zapytanie 3.
$pdo->query(...) wysyła zapytanie 3, a ->fetchAll() od razu odbiera wszystkie 6 wierszy do tablicy $cars (R.5.3). Napis PHP w cudzysłowach, bo w środku jest apostrof wokół alfa. query wystarcza, bo w zapytaniu nie ma danych od użytkownika - gdyby model wybierał użytkownik z listy, byłoby prepare i execute z parametrem.
index.phpPHPzmienione: 6 linii
<?phpfunctione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$cars = [];$randomCars = [];$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cars = $pdo->query("SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata FROM pojazdy AS p JOIN kolory AS k ON k.id = p.kolor WHERE p.model = 'alfa'" )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Konfigurator samochodów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Serwis konfiguracji samochodów</h1> </header> <nav> <h2>Samochody</h2> <h2>Konfigurator</h2> <h2>Kontakt</h2> </nav> <main> <sectionid="cars"> <table> </table> </section> <sectionid="configurator"> <table> <tr> <thcolspan="2">Konfiguracja</th> <th>Cena</th> </tr> <tr> <tdcolspan="3"><imgsrc="a1.jpg"alt="Konfiguracja 1"></td> </tr> <tr> <tdcolspan="3"><imgsrc="a2.jpg"alt="Konfiguracja 2"></td> </tr> </table> </section> <sectionid="contact"> <h3>111 222 444</h3> <imgsrc="a3.png"alt="Samochód"> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
R.2.6 W bloku sekcji lewej umieszczono tabelę z czterema kolumnami oraz w bloku sekcji środkowej - z trzema kolumnami (w skrypcie lub HTML) , zastosowane znaczniki <table>, <tr>, <td>
R.5.5 Skrypt 1 wykorzystuje pętlę, której liczba powtórzeń równa jest liczbie rekordów zwróconych zapytaniem
ok. 3 min
Polecenie z arkusza
W wierszach tabeli wyświetlane są kolejne rekordy zgodnie z ilustracją 2.
W tabeli sekcji aut pętla foreach ($cars as $car): ... endforeach; w składni alternatywnej - wykona się tyle razy, ile wierszy oddało zapytanie, czyli 6 (R.5.5). Każdy obrót to <tr> z komórkami: marka, model i nazwa koloru po nazwach kolumn ($car['nazwa']), a każda wartość przez e() (R.2.6). <?= ... ?> to skrót od <?php echo ... ?>.
Kolejność wierszy wybiera serwer (zapytanie nie ma ORDER BY); MariaDB z XAMPP-a oddaje je w kolejności identyfikatorów aut, więc „zielony metalik” stoi trzeci, a nie czwarty jak na ilustracji 2 - klucz kolejności nie ocenia. Najedź na wiersz: podświetla się na ForestGreen.
index.phpPHPzmienione: 7 linii
<?phpfunctione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$cars = [];$randomCars = [];$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cars = $pdo->query("SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata FROM pojazdy AS p JOIN kolory AS k ON k.id = p.kolor WHERE p.model = 'alfa'" )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Konfigurator samochodów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Serwis konfiguracji samochodów</h1> </header> <nav> <h2>Samochody</h2> <h2>Konfigurator</h2> <h2>Kontakt</h2> </nav> <main> <sectionid="cars"> <table><?phpforeach ($carsas$car): ?> <tr> <td><?= e($car['marka']) ?></td> <td><?= e($car['model']) ?></td> <td><?= e($car['nazwa']) ?></td> </tr><?phpendforeach; ?> </table> </section> <sectionid="configurator"> <table> <tr> <thcolspan="2">Konfiguracja</th> <th>Cena</th> </tr> <tr> <tdcolspan="3"><imgsrc="a1.jpg"alt="Konfiguracja 1"></td> </tr> <tr> <tdcolspan="3"><imgsrc="a2.jpg"alt="Konfiguracja 2"></td> </tr> </table> </section> <sectionid="contact"> <h3>111 222 444</h3> <imgsrc="a3.png"alt="Samochód"> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 22: Skrypt 1: cena całkowita w czwartej kolumnie
Kryteria: ,
R.5.4 Skrypt 1 oblicza dla każdego rekordu cenę całkowitą jako sumę ceny i dopłaty za kolor zwróconych zapytaniem
R.5.6 Skrypt 1 wyświetla w wierszach tabeli markę, model, nazwę koloru zwrócone zapytaniem i obliczoną cenę całkowitą
ok. 3 min
Polecenie z arkusza
W czwartej kolumnie tabeli wyświetlana jest cena całkowita, która jest sumą ceny z tabeli pojazdy oraz dopłaty z tabeli kolory.
Czwarta komórka: (float) $car['cena'] + (float) $car['doplata'] (R.5.4, R.5.6). PDO oddaje liczby z bazy jako napisy ("72000", "1000.00"); + w PHP i tak zamieniłby je na liczby, ale jawne (float) mówi czytającemu, co się dzieje, i nie zależy od tego, czy napis „wygląda na liczbę”. Wynik 73000.0 PHP wypisuje jako 73000 - zielony metalik za 72000 kosztuje razem 73000, jak na ilustracji 2.
Wynik liczbowy nie potrzebuje e(): liczba nie zawiera znaków HTML.
Typowe błędy
Kropka zamiast plusa ($car['cena'] . $car['doplata']) skleja napisy w „720001000.00” - w PHP kropka łączy tekst.
index.phpPHPzmienione: 1 linia
<?phpfunctione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$cars = [];$randomCars = [];$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cars = $pdo->query("SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata FROM pojazdy AS p JOIN kolory AS k ON k.id = p.kolor WHERE p.model = 'alfa'" )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Konfigurator samochodów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Serwis konfiguracji samochodów</h1> </header> <nav> <h2>Samochody</h2> <h2>Konfigurator</h2> <h2>Kontakt</h2> </nav> <main> <sectionid="cars"> <table><?phpforeach ($carsas$car): ?> <tr> <td><?= e($car['marka']) ?></td> <td><?= e($car['model']) ?></td> <td><?= e($car['nazwa']) ?></td> <td><?= (float) $car['cena'] + (float) $car['doplata'] ?></td> </tr><?phpendforeach; ?> </table> </section> <sectionid="configurator"> <table> <tr> <thcolspan="2">Konfiguracja</th> <th>Cena</th> </tr> <tr> <tdcolspan="3"><imgsrc="a1.jpg"alt="Konfiguracja 1"></td> </tr> <tr> <tdcolspan="3"><imgsrc="a2.jpg"alt="Konfiguracja 2"></td> </tr> </table> </section> <sectionid="contact"> <h3>111 222 444</h3> <imgsrc="a3.png"alt="Samochód"> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 23: Skrypt 2: zapytanie 4 i pierwsze auto w wierszach 3-4
Kryteria:
R.5.7 Skrypt 2 wyświetla dwa zwrócone z bazy rekordy: markę, model i cenę
ok. 4 min
Polecenie z arkusza
Skrypt 2: Wysyła do bazy danych zapytanie 4. Wyświetla pierwszy rekord w tabeli w wierszach 3 i 4.
w bloku danych drugie zapytanie: fetchAll() oddaje dwa wylosowane auta do $randomCars, a $pdo = null nadal stoi na końcu,
funkcja carRows(array $car) zwraca dwa wiersze tabeli dla jednego auta: „Marka” z marką i ceną scaloną na dwa wiersze (rowspan="2"), potem „Model” z modelem - już bez trzeciej komórki, bo jej miejsce zajmuje cena z wiersza wyżej (R.2.7). Każda wartość przez e(). Typ array i string w nagłówku mówią, co funkcja przyjmuje i co zwraca,
pod wierszem z a1.jpg: if (isset($randomCars[0])) - pierwsze auto, jeśli jest (po awarii bazy tablica jest pusta i wiersze się nie pojawią).
W podglądzie pierwsze auto z ilustracji 2 - BM beta za 94000 (w kopii uruchomieniowej losowanie zastąpione tymi dwoma autami).
Typowe błędy
Trzecia komórka także w wierszu z modelem rozjeżdża tabelę - przy rowspan="2" drugi wiersz ma o jedną komórkę mniej.
index.phpPHPzmienione: 10 linii
<?phpfunctione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$cars = [];$randomCars = [];$dbError = '';functioncarRows(array$car): string{return'<tr><td>Marka</td><td>' . e($car['marka']) . '</td><td rowspan="2">' . e($car['cena']) . '</td></tr>' . '<tr><td>Model</td><td>' . e($car['model']) . '</td></tr>';}try {$pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cars = $pdo->query("SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata FROM pojazdy AS p JOIN kolory AS k ON k.id = p.kolor WHERE p.model = 'alfa'" )->fetchAll();$randomCars = $pdo->query('SELECT marka, model, cena FROM pojazdy ORDER BY RAND() LIMIT 2')->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Konfigurator samochodów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Serwis konfiguracji samochodów</h1> </header> <nav> <h2>Samochody</h2> <h2>Konfigurator</h2> <h2>Kontakt</h2> </nav> <main> <sectionid="cars"> <table><?phpforeach ($carsas$car): ?> <tr> <td><?= e($car['marka']) ?></td> <td><?= e($car['model']) ?></td> <td><?= e($car['nazwa']) ?></td> <td><?= (float) $car['cena'] + (float) $car['doplata'] ?></td> </tr><?phpendforeach; ?> </table> </section> <sectionid="configurator"> <table> <tr> <thcolspan="2">Konfiguracja</th> <th>Cena</th> </tr> <tr> <tdcolspan="3"><imgsrc="a1.jpg"alt="Konfiguracja 1"></td> </tr><?phpif (isset($randomCars[0])): ?><?= carRows($randomCars[0]) ?><?phpendif; ?> <tr> <tdcolspan="3"><imgsrc="a2.jpg"alt="Konfiguracja 2"></td> </tr> </table> </section> <sectionid="contact"> <h3>111 222 444</h3> <imgsrc="a3.png"alt="Samochód"> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 24: Skrypt 2: drugie auto w wierszach 6-7
ok. 2 min
Polecenie z arkusza
Drugi rekord w wierszach 6 i 7.
Pod wierszem z a2.jpg ta sama funkcja dla $randomCars[1]. Jeden kod wypisuje oba auta - zmiana wyglądu wiersza (np. „zł” przy cenie) to jedna linia w funkcji. Tabela ma komplet siedmiu wierszy, jak na ilustracji 2. Odśwież stronę na egzaminie - auta się zmieniają.
index.phpPHPzmienione: 3 linie
<?phpfunctione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$cars = [];$randomCars = [];$dbError = '';functioncarRows(array$car): string{return'<tr><td>Marka</td><td>' . e($car['marka']) . '</td><td rowspan="2">' . e($car['cena']) . '</td></tr>' . '<tr><td>Model</td><td>' . e($car['model']) . '</td></tr>';}try {$pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cars = $pdo->query("SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata FROM pojazdy AS p JOIN kolory AS k ON k.id = p.kolor WHERE p.model = 'alfa'" )->fetchAll();$randomCars = $pdo->query('SELECT marka, model, cena FROM pojazdy ORDER BY RAND() LIMIT 2')->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Konfigurator samochodów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Serwis konfiguracji samochodów</h1> </header> <nav> <h2>Samochody</h2> <h2>Konfigurator</h2> <h2>Kontakt</h2> </nav> <main> <sectionid="cars"> <table><?phpforeach ($carsas$car): ?> <tr> <td><?= e($car['marka']) ?></td> <td><?= e($car['model']) ?></td> <td><?= e($car['nazwa']) ?></td> <td><?= (float) $car['cena'] + (float) $car['doplata'] ?></td> </tr><?phpendforeach; ?> </table> </section> <sectionid="configurator"> <table> <tr> <thcolspan="2">Konfiguracja</th> <th>Cena</th> </tr> <tr> <tdcolspan="3"><imgsrc="a1.jpg"alt="Konfiguracja 1"></td> </tr><?phpif (isset($randomCars[0])): ?><?= carRows($randomCars[0]) ?><?phpendif; ?> <tr> <tdcolspan="3"><imgsrc="a2.jpg"alt="Konfiguracja 2"></td> </tr><?phpif (isset($randomCars[1])): ?><?= carRows($randomCars[1]) ?><?phpendif; ?> </table> </section> <sectionid="contact"> <h3>111 222 444</h3> <imgsrc="a3.png"alt="Samochód"> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 25: Komunikat, gdy baza nie odpowiada
ok. 3 min
Polecenie z arkusza
Na końcu zamykane jest połączenie z serwerem.
W tabeli aut wiersz z komunikatem z $dbError - tylko gdy catch coś do niego wpisał. Sprawdzone uruchomieniem z błędną nazwą bazy: strona się wyświetla, w sekcji aut stoi „Nie udało się pobrać danych o samochodach…”, tabela konfiguratora ma same obrazy, a w treści nie ma ani nazwy bazy, ani kodu błędu SQL. Bez try/catch PHP przerwałby stronę w połowie komunikatem „Fatal error: Uncaught PDOException” z pełną ścieżką pliku.
W normalnym działaniu połączenie jest zamykane ($pdo = null) zaraz po dwóch zapytaniach, przed pierwszą linią HTML - ostatnią operacją na bazie, jak chce arkusz. Podgląd: strona po prawdziwym uruchomieniu, bez komunikatu.
Krok 26 · po tym etapie masz 34 z 34 kryteriów (pkt)
Krok 26: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 2 min
Obie wersje dają komplet 34 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ sekcji
float: left + clear: both w stopce
display: grid i repeat(3, 33.3%) w bloku głównym
baza
mysqli_connect, mysqli_fetch_row, $row[3]
PDO z wyjątkami, fetchAll, $car['nazwa']
gdzie PHP
rozproszone po HTML-u, połączenie zamykane po stopce
dane na górze pliku, połączenie zamknięte przed HTML-em
pętla
while + echo całego wiersza w napisie
foreach w szablonie, HTML poza napisami
wylosowane auta
dwie kopie echo (w dobrych praktykach funkcja)
funkcja carRows() od początku
dane na stronie
wprost (w dobrych praktykach htmlspecialchars)
e() przy każdej wartości
awaria bazy
biała strona z błędem
komunikat w tabeli, szczegóły w error_log
Na egzaminie wersja podstawowa trzyma się dosłownie funkcji z tabeli 1 arkusza i jest szybsza do napisania. Nowoczesna oddziela pobieranie danych od wyglądu - przy większej stronie łatwiej ją zmieniać i testować. Czego tu świadomie nie ma: pliku config.php z danymi logowania do bazy (arkusz każe szukać połączenia w index.php) i zapytań przygotowanych - w tej stronie żadna wartość nie przychodzi od użytkownika.
Krok 27 · po tym etapie masz 34 z 34 kryteriów (pkt)
Krok 27: Plik przeglądarka.txt i nagranie płyty
ok. 5 min
Polecenie z arkusza
Po zakończeniu pracy utwórz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Umieść go w folderze z numerem zdającego. Nagraj płytę z rezultatami pracy. W folderze z numerem zdającego, powinny znajdować się pliki: a1.jpg, a2.jpg, a3.png, import.jpg, index.php, kw1.png, kw2.png, kw3.png, kw4.png, kwerendy.txt, przeglądarka.txt, styl.css, ewentualnie inne przygotowane pliki.
W przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana przez http://localhost/.... Sprawdź listę plików z arkusza (import w JPEG, reszta zrzutów w PNG), nagraj płytę i sprawdź jej odczyt.
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.