INF.03-04-23.01-SGStyczeń 2023 · 33 kroki · ok. 64 min · 32 kryteria z klucza
Rozwiązanie krok po kroku: Portal biura podróży (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza biuro z czterema zapytaniami i strona wycieczki.php z listą wolnych wycieczek, tabelą bestsellerów, galerią zdjęć z bazy i kontaktem - i tak samo spełnia każde kryterium klucza. Inna jest droga:
siatka CSS zamiast float: cały układ opisuje jedna reguła dla <body> - kolumny 25%, 50% i 25% oraz mapa obszarów (grid-template-areas) z blokiem danych na całą szerokość, bez clear przy stopce,
znaczniki opisujące treść (<main>, <aside>, <section>) i kolory z arkusza w zmiennych CSS,
zapytania pisane czytelnie: klauzule w osobnych liniach, aliasy tabel, stała kolejność wierszy (ORDER BY),
PHP na PDO: oba zapytania strona wykonuje na górze pliku wycieczki.php, przed HTML-em, w jednym bloku try, a w HTML-u zostają tylko pętle foreach i bezpieczne wypisywanie (htmlspecialchars w funkcji e()),
komunikaty zamiast pustej listy, gdy nie ma wolnych miejsc albo bazy nie da się odczytać.
Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i zdjęcia pochodzą z prawdziwego archiwum zad4, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 na bazie z arkusza; najechanie kursorem na zdjęcie podświetla jego tło, jak na obrazie 2.
Jak liczą się punkty. Klucz ma 32 kryteria (R.1.1 - R.5.5), każde za 1 punkt. Próg zdania to 75%, czyli 24 punkty. 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 32 kryteriów (pkt)
Krok 1: Baza danych biuro
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie biuro
Rozpakuj zad4.zip z pulpitu (hasło &poDrozE$ - z ampersandem na początku i dolarem na końcu) do folderu z numerem zdającego: egzamin3.sql i zdjęcia 1.jpg - 9.jpg. W phpMyAdmin Nowa → biuro albo zapytaniem:
CREATE DATABASE biuro CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;
Arkusz nie podaje porównywania. Tabele z egzamin3.sql mają zestaw znaków zapisany we własnej definicji (wycieczki i zdjecia - latin1), więc ustawienie bazy ich nie zmienia.
Typowe błędy
Nazwa bazy musi brzmieć dokładnie biuro (nie egzamin3) - skrypt łączy się z bazą po nazwie (R.5.1).
Krok 2: Import tabel z pliku egzamin3.sql
ok. 2 min
Polecenie z arkusza
Do bazy biuro zaimportuj tabele z pliku egzamin3.sql z rozpakowanego archiwum
Zaznaczona baza biuro, Import → egzamin3.sql → Wykonaj. Plik ma osiem tabel z różnych zadań; tu potrzebne są dwie z obrazu 1: wycieczki (9 wycieczek z polem dostepna równym 1 albo 0) i zdjecia (9 plików z podpisami), połączone polem zdjecia_id.
Krok 3: Zrzut ekranu import.png
Kryteria:
R.1.1 Wykonano import tabel do bazy danych biuro czynność udokumentowano plikiem o nazwie import w formacie PNG
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie PNG pod nazwą import. Nie kadruj zrzutu. Zrzut powinien 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 PNG, pod nazwą import. Zrzuty zapytań kw1 - kw4 będą już w JPEG.
Typowe błędy
Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany (uwaga w kluczu przy R.1).
Krok 4: Zapytanie 1: dostępne wycieczki
Kryteria: ,
R.1.2 Zapisano plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Utworzono zapytanie 1 wybierające jedynie pola id, dataWyjazdu, cel i cena z tabeli wycieczki dla dostępnych wycieczek. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE; (lub =1) oraz wynik działania udokumentowano zrzutem, na którym widocznych jest dokładnie 6 rekordów o id = 1, 2, 3, 4, 6, 9, jedynie pola id, dataWyjazdu, cel i cena
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie pola id, dataWyjazdu, cel i cena z tabeli wycieczki dla dostępnych wycieczek
WHERE dostepna = TRUE - w MariaDB TRUE to 1 (klucz uznaje oba zapisy). ORDER BY id ustala kolejność wierszy: bez sortowania serwer może je oddać w dowolnej kolejności, a lista na stronie ma iść od pierwszej wycieczki. Wynik: 6 wycieczek o id 1, 2, 3, 4, 6 i 9 - jak w kluczu. Zapytanie do kwerendy.txt, zrzut kw1.jpg.
R.1.4 Utworzono zapytanie 2 wybierające jedynie pola nazwaPliku i podpis z tabeli zdjecia , sortując je malejąco według kolumny podpis. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwaPliku, podpis FROM zdjecia ORDER BY podpis DESC; oraz wynik działania udokumentowano zrzutem, na którym widocznych jest dokładnie 9 rekordów dla kolejnych nazw plików 2, 4, 6, 7, 9, 1, 5, 3, 8, jedynie pola nazwaPliku i podpis
ok. 1 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie pola nazwaPliku i podpis z tabeli zdjecia, sortując je malejąco według kolumny podpis
ORDER BY podpis DESC - od Z do A. Wynik: pliki w kolejności 2, 4, 6, 7, 9, 1, 5, 3, 8 (od Wenecji do Barcelony) - jak w kluczu i w galerii na obrazie 2. Zrzut: kw2.jpg.
Krok 6: Zapytanie 3: wycieczki droższe niż 1200 zł z podpisem zdjęcia
Kryteria:
R.1.5 Utworzono zapytanie 3 wybierające jedynie cel i cenę wycieczki z tabeli wycieczki oraz odpowiadający im podpis z tabeli zdjecia dla wycieczek, których cena jest wyższa niż 1200 zł. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT cel, cena, podpis FROM zdjecia JOIN wycieczki ON zdjecia.id = wycieczki.zdjecia_id WHERE cena > 1200; (możliwe też INNER JOIN lub porównanie kluczy po WHERE) oraz wynik działania udokumentowano zrzutem, na którym widocznych jest dokładnie 5 rekordów: Francja, Paryz 1300 Paryz; Francja, Paryz 1350 Paryz; Hiszpania, Barcelona 1500 Barcelona; Hiszpania, Barcelona 1500 Barcelona; Hiszpania, Barcelona 1400 Barcelona
ok. 3 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie cel i cenę wycieczki z tabeli wycieczki oraz odpowiadający im podpis z tabeli zdjecia dla wycieczek, których cena jest wyższa niż 1200 zł. Zapytanie wykorzystuje relację
aliasy tabelw i z - obie mają pole id, a przedrostek mówi, o które chodzi,
JOIN ... ON z.id = w.zdjecia_id - relacja z obrazu 1, zapisana przy złączeniu,
WHERE w.cena > 1200 - „wyższa niż”, więc wycieczki za dokładnie 1200 zł odpadają; ORDER BY w.id - kolejność jak w tabeli.
Wynik: 5 wierszy - Paryż 1300 i 1350, Barcelona 1500, 1500 i 1400 - jak w kluczu. Zrzut: kw3.jpg.
kwerendy.txtTekstzmienione: 7 linii
-- Zapytanie 1SELECT id, dataWyjazdu, cel, cenaFROM wycieczkiWHERE dostepna = TRUEORDER BY id;-- Zapytanie 2SELECT nazwaPliku, podpisFROM zdjeciaORDER BY podpis DESC;-- Zapytanie 3SELECT w.cel, w.cena, z.podpisFROM wycieczki AS wJOIN zdjecia AS z ON z.id = w.zdjecia_idWHERE w.cena > 1200ORDER BY w.id;
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: pole liczbaDni po dataWyjazdu
Kryteria:
R.1.6 Utworzono zapytanie 4 dodające do tabeli wycieczki pole liczbaDni przyjmujące tylko liczby naturalne, pole powinno być wstawione po polu dataWyjazdu. W pliku z kwerendami lub na zrzucie istnieje zapis np.: ALTER TABLE wycieczki ADD liczbaDni int UNSIGNED AFTER dataWyjazdu; (lub ADD COLUMN, dowolny typ liczbowy całkowity, UNSIGNED obowiązkowe) oraz wynik działania udokumentowano zrzutem, na którym widnieje komunikat o poprawnym dodaniu lub struktura tabeli z dodaną kolumną
ok. 2 min
Polecenie z arkusza
Zapytanie 4: dodające do tabeli wycieczki pole liczbaDni przyjmujące tylko liczby naturalne, pole powinno być wstawione po polu dataWyjazdu
ADD COLUMN liczbaDni - nowe pole,
TINYINT UNSIGNED - najmniejszy typ całkowity, bez znaku: 0 - 255 dni, a wycieczka nie trwa dłużej; klucz przyjmuje dowolny typ całkowity, byle z UNSIGNED,
AFTER dataWyjazdu - miejsce pola w tabeli; bez tego trafiłoby na koniec.
Struktura po zmianie ma liczbaDni zaraz po dataWyjazdu; zapytanie 1 daje dalej ten sam wynik, bo wybiera pola po nazwach (sprawdzone). Zrzut kw4.jpg: komunikat i struktura tabeli.
Typowe błędy
Bez UNSIGNED klucz nie uzna zapytania - samo INT albo TINYINT przyjmuje liczby ujemne.
kwerendy.txtTekstzmienione: 4 linie
-- Zapytanie 1SELECT id, dataWyjazdu, cel, cenaFROM wycieczkiWHERE dostepna = TRUEORDER BY id;-- Zapytanie 2SELECT nazwaPliku, podpisFROM zdjeciaORDER BY podpis DESC;-- Zapytanie 3SELECT w.cel, w.cena, z.podpisFROM wycieczki AS wJOIN zdjecia AS z ON z.id = w.zdjecia_idWHERE w.cena > 1200ORDER BY w.id;-- Zapytanie 4ALTER TABLE wycieczkiADD COLUMN liczbaDni TINYINT UNSIGNED AFTER dataWyjazdu;
Podgląd strony po tym kroku
Etap 2: Strona HTML
Kroki 8-17 · po tym etapie masz 14 z 32 kryteriów (pkt)
R.2.1 Witrynę internetową zapisano w pliku PHP o nazwie wycieczki
R.2.2 Zapisano jawnie standard kodowania polskich znaków i deklarację HTML5 <!DOCTYPE HTML> i zadeklarowano język witryny: polski np. <html lang="pl">
ok. 2 min
Polecenie z arkusza
Składa się ze strony o nazwie wycieczki.php. Zapisana w języku HTML 5. Jawnie zastosowany właściwy standard kodowania polskich znaków. Zadeklarowany język witryny: polski.
<!DOCTYPE html>, <html lang="pl"> i <meta charset="UTF-8"> jako pierwszy element <head> (R.2.1, R.2.2). Rozszerzenie .php, bo dojdą skrypty - strona działa przez http://localhost/..., a zdjęcia 1.jpg - 9.jpg leżą w tym samym folderze.
R.2.3 Nadano tytuł stronie: "Wycieczki po Europie"
ok. 1 min
Polecenie z arkusza
Tytuł strony widoczny na karcie przeglądarki: „Wycieczki po Europie”
<title> (R.2.3) i viewport - telefon nie pomniejsza strony do szerokości ekranu komputera.
wycieczki.phpPHPzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Wycieczki po Europie</title></head><body></body></html>
Podgląd strony po tym kroku (wycieczki.php)
Krok 10: Podpięcie arkusza stylów
Kryteria:
R.3.3 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl4.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 styl4.css prawidłowo połączony z kodem strony
Nazwa z cyfrą 4: styl4.css. Całe formatowanie pochodzi z tego pliku (R.3.3).
Typowe błędy
Klucz odbiera R.3.3 także wtedy, gdy choć jeden selektor nie trafia w blok (np. kropka zamiast #).
wycieczki.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Wycieczki po Europie</title> <linkrel="stylesheet"href="styl4.css"></head><body></body></html>
Podgląd strony po tym kroku (wycieczki.php)
Krok 11: Bloki: baner, dane, trzy bloki, stopka
Kryteria:
R.2.4 Układ strony zdefiniowano dzieląc ją na bloki: baner, dane, lewy, środkowy, prawy i stopka. Zastosowano znaczniki sekcji
ok. 2 min
Polecenie z arkusza
Podział strony na bloki: na górze baner, poniżej blok z danymi, poniżej obok siebie trzy bloki: lewy, środkowy i prawy, poniżej stopka. Podział zrealizowany za pomocą znaczników sekcji tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z Obrazem 2
Każdy blok w znaczniku, który mówi, co zawiera (R.2.4):
<header> - baner, <footer> - stopka,
<main id="trips"> - blok z danymi: lista wolnych wycieczek, główna treść strony,
<aside id="bestsellers"> i <aside id="contact"> - bloki boczne z informacją dodatkową,
<section id="gallery"> - galeria zdjęć z własnym nagłówkiem.
Identyfikatory nazywają treść, nie położenie - dwa <aside> trzeba w CSS odróżnić, a nazwa #contact pasuje także wtedy, gdy blok zmieni miejsce.
Zawartość bloku z danymi: Nagłówek trzeciego stopnia o treści „Wycieczki, na które są wolne miejsca”. Lista punktowana (nieuporządkowana) wypełniona skryptem 1
<h3> - arkusz tak ustawia stopnie nagłówków - i pusta lista <ul>; elementy <li> wypisze Skrypt 1.
wycieczki.phpPHPzmienione: 3 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Wycieczki po Europie</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>BIURO TURYSTYCZNE</h1> </header> <mainid="trips"> <h3>Wycieczki, na które są wolne miejsca</h3> <ul> </ul> </main> <asideid="bestsellers"> </aside> <sectionid="gallery"> </section> <asideid="contact"> </aside> <footer> </footer></body></html>
Podgląd strony po tym kroku (wycieczki.php)
Krok 14: Blok lewy: nagłówek i tabela bestsellerów
Kryteria:
R.2.7 W bloku lewym zapisano tabelę o dwóch kolumnach i trzech wierszach, zastosowano znaczniki <table>, <tr>, <td>
ok. 2 min
Polecenie z arkusza
Zawartość lewego bloku: Nagłówek drugiego stopnia o treści: „Bestselery”. Tabela o dwóch kolumnach i trzech wierszach wypełniona danymi: Wenecja - kwiecień, Londyn - lipiec, Rzym - wrzesień
<h2> z napisem dokładnie jak w arkuszu („Bestselery” przez jedno „l”) i tabela: trzy wiersze <tr> po dwie komórki <td>, każda w osobnej linii kodu - łatwo policzyć kolumny (R.2.7).
R.2.5 W banerze zapisano nagłówek h1: "BIURO TURYSTYCZNE", w bloku dane h3: "Wycieczki, na które są wolne miejsca", w blokach lewym, środkowym i prawym przynajmniej jeden h2 z: "Bestselery", "Nasze zdjęcia", "Skontaktuj się" oraz przynajmniej jeden paragraf
Zawartość prawego bloku: Nagłówek drugiego stopnia o treści: „Skontaktuj się”. Odnośnik do adresu e-mail [email protected] o treści: „napisz do nas”. Paragraf (akapit) o treści: „telefon: 111222333”
Odnośnik mailto: otwiera program pocztowy z wpisanym odbiorcą (R.3.2). W akapicie numer jest odnośnikiem tel: z numerem kierunkowym kraju - na telefonie dotknięcie od razu dzwoni, a tekst akapitu dalej brzmi „telefon: 111222333”. Strona ma <h1>, <h3>, <h2> i akapit (R.2.5).
Kroki 18-25 · po tym etapie masz 25 z 32 kryteriów (pkt)
Krok 18: Plik styl4.css: zmienne i krój czcionki
Kryteria:
R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (*): krój czcionki Helvetica
ok. 2 min
Polecenie z arkusza
Ustawione domyślne wartości dla wszystkich selektorów stylu CSS: krój czcionki Helvetica
Trzy kolory tła z arkusza w zmiennych - #FFFFCE ma i blok z danymi, i blok środkowy, #B3BC6D baner, stopka i tło obrazu pod kursorem. Selektor * z Helveticą; w Windows jej zwykle nie ma, więc dalej stoją Arial i rodzina 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 flex albo grid)
ok. 3 min
Polecenie z arkusza
Podział strony na bloki: na górze baner, poniżej blok z danymi, poniżej obok siebie trzy bloki: lewy, środkowy i prawy, poniżej stopka. Podział zrealizowany za pomocą znaczników sekcji tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z Obrazem 2
display: grid na <body> i mapa strony w grid-template-areas - cztery rzędy: baner, blok z danymi i stopka zajmują wszystkie trzy kolumny, a w trzecim rzędzie stoją trzy bloki. grid-template-columns: 25% 50% 25% to szerokości z arkusza (klucz przy R.4.4 przyjmuje je zapisane w ten sposób). grid-area przypisuje każdy blok do obszaru.
W wersji z float trzeba było uważać, żeby nie dać float blokowi z danymi - tu każdy blok stoi tam, gdzie wskazuje mapa (R.3.1).
Typowe błędy
Każdy obszar w mapie musi tworzyć prostokąt, a każdy wiersz mapy mieć tyle nazw, ile jest kolumn - inaczej przeglądarka odrzuca całą deklarację grid-template-areas i bloki układają się jeden pod drugim.
R.4.2 Ustawiono kolor tła #B3BC6D dla banera i stopki, #FFFFCE dla bloku z danymi i środkowego, #E6EE9C dla bloku lewego i prawego
ok. 2 min
Polecenie z arkusza
Dla bloku lewego i prawego: kolor tła #E6EE9C, szerokość 25%, wysokość 450 px, wyrównanie tekstu do środka. Dla bloku środkowego: kolor tła #FFFFCE, szerokość 50%, wysokość 450 px
Szerokości 25%, 50% i 25% są już w kolumnach siatki. Tu kolory i wysokości - komplet kolorów tła (R.4.2).
Typowe błędy
width: 50% dopisane jeszcze na #gallery liczy się w siatce od kolumny - galeria miałaby 50% z 50% strony.
R.4.4 Ustawiono szerokość 25% dla bloków lewego i prawego, 50% dla środkowego (width lub grid-template-columns lub flex), 70% dla selektora table
R.4.8 Ustawiono obramowanie dla tabeli: 1px solid Olive
ok. 1 min
Polecenie z arkusza
Dla selektora tabeli: obramowanie linią ciągłą o szerokości 1 px i kolorze Olive, marginesy zewnętrzne automatycznie wyliczane przez przeglądarkę, szerokość 70%
Selektor znacznika table, jak każe uwaga arkusza. margin: auto wyśrodkowuje tabelę w bloku; ramkę ma sama tabela, komórki nie - jak na obrazie 2. Szerokości z arkusza są komplet (R.4.4, R.4.8).
Krok 24: Nagłówki drugiego stopnia i ich pierwsza litera
Kryteria: ,
R.4.7 Ustawiono wyrównanie tekstu do środka dla banera, stopki, bloku lewego, prawego, selektora h2
R.4.9 Ustawiono styl pseudoelementu first-letter ustawionego dla h2: rozmiar czcionki 150%, kolor czcionki Olive
ok. 1 min
Polecenie z arkusza
Dla selektora nagłówka drugiego stopnia: wyrównanie tekstu do środka. Dla pierwszej litery nagłówka drugiego stopnia: rozmiar czcionki 150%, kolor czcionki Olive
Selektor znacznika h2 wyśrodkowuje trzy nagłówki (R.4.7). Pseudoelement ::first-letter to sama pierwsza litera - „B”, „N” i „S” są większe i oliwkowe (R.4.9).
R.4.5 Ustawiono wysokość 450px dla bloków lewego, środkowego i prawego i 100px dla selektora img
R.4.6 Ustawiono marginesy wewnętrzne (padding) 5px dla banera i stopki, 7px dla obrazu, 10px górny i dolny i 150px prawy i lewy dla bloku z danymi oraz marginesy zewnętrzne (margin) auto dla tabeli
R.4.10 Ustawiono styl pseudoklasy hover dla obrazu: kolor tła #B3BC6D
ok. 1 min
Polecenie z arkusza
Dla selektora obrazu: wysokość 100 px, marginesy wewnętrzne 7 px. W momencie, gdy kursor znajduje się na obrazie, styl obrazu zmienia się na: kolor tła #B3BC6D
padding: 7px to pas wokół zdjęcia wewnątrz elementu - pod kursorem (:hover) zabarwia się kolorem banera, dlatego na obrazie 2 środkowe zdjęcie ma zieloną ramkę (R.4.5, R.4.6, R.4.10).
Kroki 26-31 · po tym etapie masz 32 z 32 kryteriów (pkt)
Krok 26: Połączenie z bazą na górze pliku i zamknięcie
Kryteria: ,
R.5.1 Skrypt zawiera działające połączenie z serwerem bazy danych i wybór bazy biuro
R.5.2 Ostatnią operacją na bazie jest jej zamknięcie (zgodne ze składnią)
ok. 4 min
Polecenie z arkusza
Łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie biuro. Na końcu działania skrypt zamyka połączenie z serwerem.
Oba skrypty z arkusza pobierają dane w jednym bloku PHP na górze wycieczki.php, przed HTML-em - połączenie otwiera się raz, oba zapytania wykonują się jedno po drugim, a połączenie jest zamknięte, zanim przeglądarka dostanie pierwszy znacznik. W HTML-u zostaną tylko pętle wypisujące wynik.
new PDO(...) - serwer localhost, baza biuro, 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 na końcu bloku - PDO zamyka połączenie, gdy znika odwołanie do obiektu; zostanie ostatnią operacją na bazie (R.5.2),
try / catch (PDOException ...) - przy awarii bazy uczeń zobaczy zwykłe zdanie, a nie szczegóły 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.
wycieczki.phpPHPzmienione: 12 linii
<?phptry {$pdo = new PDO('mysql:host=localhost;dbname=biuro;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ę pobrać danych z bazy. 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>Wycieczki po Europie</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>BIURO TURYSTYCZNE</h1> </header> <mainid="trips"> <h3>Wycieczki, na które są wolne miejsca</h3> <ul> </ul> </main> <asideid="bestsellers"> <h2>Bestselery</h2> <table> <tr> <td>Wenecja</td> <td>kwiecień</td> </tr> <tr> <td>Londyn</td> <td>lipiec</td> </tr> <tr> <td>Rzym</td> <td>wrzesień</td> </tr> </table> </aside> <sectionid="gallery"> <h2>Nasze zdjęcia</h2> </section> <asideid="contact"> <h2>Skontaktuj się</h2> <ahref="mailto:[email protected]">napisz do nas</a> <p>telefon: <ahref="tel:+48111222333">111222333</a></p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wycieczki.php)
Krok 27: Skrypt 1: zapytanie 1
Kryteria:
R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 1 lub skrypt 2 wysyła zapytanie 2
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 listę sześciu wycieczek z kluczami id, dataWyjazdu, cel, cena. W catch lista dostaje wartość pustą, żeby HTML niżej nie trafił na niezdefiniowaną zmienną (R.5.3). Na stronie jeszcze nic nie widać.
wycieczki.phpPHPzmienione: 4 linie
<?phptry {$pdo = new PDO('mysql:host=localhost;dbname=biuro;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - wycieczki z wolnymi miejscami$trips = $pdo->query('SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE ORDER BY id')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$trips = [];$databaseError = 'Nie udało się pobrać danych z bazy. 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>Wycieczki po Europie</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>BIURO TURYSTYCZNE</h1> </header> <mainid="trips"> <h3>Wycieczki, na które są wolne miejsca</h3> <ul> </ul> </main> <asideid="bestsellers"> <h2>Bestselery</h2> <table> <tr> <td>Wenecja</td> <td>kwiecień</td> </tr> <tr> <td>Londyn</td> <td>lipiec</td> </tr> <tr> <td>Rzym</td> <td>wrzesień</td> </tr> </table> </aside> <sectionid="gallery"> <h2>Nasze zdjęcia</h2> </section> <asideid="contact"> <h2>Skontaktuj się</h2> <ahref="mailto:[email protected]">napisz do nas</a> <p>telefon: <ahref="tel:+48111222333">111222333</a></p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 28: Skrypt 1: wycieczka w elemencie listy według wzoru
Kryteria: ,
R.5.4 Skrypt 1 wyświetla każdy zwrócony zapytaniem wiersz w formacie: „<id>. dnia <dataWyjazdu> jedziemy do <cel>, cena: <cena>”, gdzie < > oznacza pole zwrócone zapytaniem
R.2.6 W bloku z danymi umieszczono listę punktowaną, zastosowano znaczniki <ul>, <li> (w skrypcie lub HTML)
ok. 3 min
Polecenie z arkusza
Każdy zwrócony zapytaniem wiersz jest wyświetlany w elemencie listy, według wzoru: „<id>. dnia <dataWyjazdu> jedziemy do <cel>, cena: <cena>”, gdzie w znakach < > zapisano pola zwrócone zapytaniem
foreach z dwukropkiem i endforeach - jeden obieg pętli to jeden element <li>; kropka, „dnia”, „jedziemy do” i przecinek to zwykły tekst wzoru między wstawkami,
pola po nazwach ($trip['cel']), a funkcja e() przepuszcza każdy tekst z bazy przez htmlspecialchars,
data w znaczniku <time datetime="..."> - na stronie wygląda tak samo, a wyszukiwarki i czytniki wiedzą, że to data.
Podgląd: sześć wycieczek, od „1. dnia 2019-09-08 jedziemy do Wlochy, Wenecja, cena: 1200” - jak na obrazie 2 (R.5.4, R.2.6).
wycieczki.phpPHPzmienione: 8 linii
<?phpfunctione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=biuro;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - wycieczki z wolnymi miejscami$trips = $pdo->query('SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE ORDER BY id')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$trips = [];$databaseError = 'Nie udało się pobrać danych z bazy. 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>Wycieczki po Europie</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>BIURO TURYSTYCZNE</h1> </header> <mainid="trips"> <h3>Wycieczki, na które są wolne miejsca</h3> <ul><?phpforeach ($tripsas$trip): ?> <li><?= e($trip['id']) ?>. dnia <timedatetime="<?= e($trip['dataWyjazdu']) ?>"><?= e($trip['dataWyjazdu']) ?></time> jedziemy do <?= e($trip['cel']) ?>, cena: <?= e($trip['cena']) ?></li><?phpendforeach; ?> </ul> </main> <asideid="bestsellers"> <h2>Bestselery</h2> <table> <tr> <td>Wenecja</td> <td>kwiecień</td> </tr> <tr> <td>Londyn</td> <td>lipiec</td> </tr> <tr> <td>Rzym</td> <td>wrzesień</td> </tr> </table> </aside> <sectionid="gallery"> <h2>Nasze zdjęcia</h2> </section> <asideid="contact"> <h2>Skontaktuj się</h2> <ahref="mailto:[email protected]">napisz do nas</a> <p>telefon: <ahref="tel:+48111222333">111222333</a></p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wycieczki.php)
Krok 29: Skrypt 2: zapytanie 2
ok. 1 min
Polecenie z arkusza
Skrypt 2: Wysyła do bazy danych zapytanie 2
Drugie zapytanie na tym samym połączeniu, przed $pdo = null - zamknięcie zostaje ostatnią operacją. Każdy wynik ma własną zmienną o nazwie mówiącej, co zawiera ($trips, $photos), więc nic nie jest nadpisywane.
wycieczki.phpPHPzmienione: 4 linie
<?phpfunctione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=biuro;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - wycieczki z wolnymi miejscami$trips = $pdo->query('SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE ORDER BY id')->fetchAll();// Skrypt 2: zapytanie 2 - zdjęcia do galerii$photos = $pdo->query('SELECT nazwaPliku, podpis FROM zdjecia ORDER BY podpis DESC')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$trips = [];$photos = [];$databaseError = 'Nie udało się pobrać danych z bazy. 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>Wycieczki po Europie</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>BIURO TURYSTYCZNE</h1> </header> <mainid="trips"> <h3>Wycieczki, na które są wolne miejsca</h3> <ul><?phpforeach ($tripsas$trip): ?> <li><?= e($trip['id']) ?>. dnia <timedatetime="<?= e($trip['dataWyjazdu']) ?>"><?= e($trip['dataWyjazdu']) ?></time> jedziemy do <?= e($trip['cel']) ?>, cena: <?= e($trip['cena']) ?></li><?phpendforeach; ?> </ul> </main> <asideid="bestsellers"> <h2>Bestselery</h2> <table> <tr> <td>Wenecja</td> <td>kwiecień</td> </tr> <tr> <td>Londyn</td> <td>lipiec</td> </tr> <tr> <td>Rzym</td> <td>wrzesień</td> </tr> </table> </aside> <sectionid="gallery"> <h2>Nasze zdjęcia</h2> </section> <asideid="contact"> <h2>Skontaktuj się</h2> <ahref="mailto:[email protected]">napisz do nas</a> <p>telefon: <ahref="tel:+48111222333">111222333</a></p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
R.5.5 Skrypt 2 wyświetla obrazy na podstawie danych zwróconych zapytaniem, w ten sposób, że pole nazwaPliku jest źródłem obrazu, a pole podpis jest tekstem alternatywnym obrazu
R.2.8 W bloku środkowym umieszczono przynajmniej jeden obraz (w HTML lub skrypcie)
ok. 3 min
Polecenie z arkusza
Dane z każdego zwróconego zapytaniem wiersza są wykorzystane do wyświetlenia kolejnych obrazów, w ten sposób, że pole nazwaPliku jest źródłem obrazu, a pole podpis jest tekstem alternatywnym obrazu
Jeden <img> na wiersz: nazwaPliku w src, podpis w alt (R.5.5, R.2.8). Wartości z bazy stoją w atrybutach w cudzysłowach, więc e() jest tu szczególnie potrzebne - ENT_QUOTES zamienia cudzysłów z bazy na encję i podpis typu Rzym "Wieczne Miasto" nie zamknie atrybutu w połowie. Atrybut title z tym samym podpisem pokazuje dymek z nazwą miasta po najechaniu kursorem.
Podgląd: galeria z obrazu 2, od Wenecji do Barcelony; najedź na zdjęcie - tło wokół niego zmieni kolor.
wycieczki.phpPHPzmienione: 3 linie
<?phpfunctione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=biuro;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - wycieczki z wolnymi miejscami$trips = $pdo->query('SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE ORDER BY id')->fetchAll();// Skrypt 2: zapytanie 2 - zdjęcia do galerii$photos = $pdo->query('SELECT nazwaPliku, podpis FROM zdjecia ORDER BY podpis DESC')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$trips = [];$photos = [];$databaseError = 'Nie udało się pobrać danych z bazy. 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>Wycieczki po Europie</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>BIURO TURYSTYCZNE</h1> </header> <mainid="trips"> <h3>Wycieczki, na które są wolne miejsca</h3> <ul><?phpforeach ($tripsas$trip): ?> <li><?= e($trip['id']) ?>. dnia <timedatetime="<?= e($trip['dataWyjazdu']) ?>"><?= e($trip['dataWyjazdu']) ?></time> jedziemy do <?= e($trip['cel']) ?>, cena: <?= e($trip['cena']) ?></li><?phpendforeach; ?> </ul> </main> <asideid="bestsellers"> <h2>Bestselery</h2> <table> <tr> <td>Wenecja</td> <td>kwiecień</td> </tr> <tr> <td>Londyn</td> <td>lipiec</td> </tr> <tr> <td>Rzym</td> <td>wrzesień</td> </tr> </table> </aside> <sectionid="gallery"> <h2>Nasze zdjęcia</h2><?phpforeach ($photosas$photo): ?> <imgsrc="<?= e($photo['nazwaPliku']) ?>"alt="<?= e($photo['podpis']) ?>"title="<?= e($photo['podpis']) ?>"><?phpendforeach; ?> </section> <asideid="contact"> <h2>Skontaktuj się</h2> <ahref="mailto:[email protected]">napisz do nas</a> <p>telefon: <ahref="tel:+48111222333">111222333</a></p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wycieczki.php)
Krok 31: Brak wolnych miejsc i awaria bazy: komunikaty
ok. 3 min
Dodatek spoza arkusza - klucz go nie ocenia. Nad listą pojawia się zdanie w dwóch sytuacjach, których obraz 2 nie pokazuje:
żadna wycieczka nie ma wolnych miejsc (sprawdzone na bazie po UPDATE wycieczki SET dostepna = 0) - zamiast pustej listy pod nagłówkiem „Wycieczki, na które są wolne miejsca”,
awaria bazy - komunikat z role="alert"; tabela bestsellerów i kontakt są zwykłym HTML-em, więc działają dalej.
Sprawdzone na prawdziwym PHP, bez ostrzeżeń. Podgląd: strona na bazie z arkusza.
wycieczki.phpPHPzmienione: 5 linii
<?phpfunctione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=biuro;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - wycieczki z wolnymi miejscami$trips = $pdo->query('SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE ORDER BY id')->fetchAll();// Skrypt 2: zapytanie 2 - zdjęcia do galerii$photos = $pdo->query('SELECT nazwaPliku, podpis FROM zdjecia ORDER BY podpis DESC')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$trips = [];$photos = [];$databaseError = 'Nie udało się pobrać danych z bazy. 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>Wycieczki po Europie</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>BIURO TURYSTYCZNE</h1> </header> <mainid="trips"> <h3>Wycieczki, na które są wolne miejsca</h3><?phpif (isset($databaseError)): ?> <prole="alert"><?= e($databaseError) ?></p><?phpelseif ($trips === []): ?> <p>Na razie nie ma wolnych miejsc - zajrzyj tu za kilka dni.</p><?phpendif; ?> <ul><?phpforeach ($tripsas$trip): ?> <li><?= e($trip['id']) ?>. dnia <timedatetime="<?= e($trip['dataWyjazdu']) ?>"><?= e($trip['dataWyjazdu']) ?></time> jedziemy do <?= e($trip['cel']) ?>, cena: <?= e($trip['cena']) ?></li><?phpendforeach; ?> </ul> </main> <asideid="bestsellers"> <h2>Bestselery</h2> <table> <tr> <td>Wenecja</td> <td>kwiecień</td> </tr> <tr> <td>Londyn</td> <td>lipiec</td> </tr> <tr> <td>Rzym</td> <td>wrzesień</td> </tr> </table> </aside> <sectionid="gallery"> <h2>Nasze zdjęcia</h2><?phpforeach ($photosas$photo): ?> <imgsrc="<?= e($photo['nazwaPliku']) ?>"alt="<?= e($photo['podpis']) ?>"title="<?= e($photo['podpis']) ?>"><?phpendforeach; ?> </section> <asideid="contact"> <h2>Skontaktuj się</h2> <ahref="mailto:[email protected]">napisz do nas</a> <p>telefon: <ahref="tel:+48111222333">111222333</a></p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 32 · po tym etapie masz 32 z 32 kryteriów (pkt)
Krok 32: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 3 min
Obie wersje dają komplet 32 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ
float trzech bloków (bez bloku danych) i clear
mapa grid-template-areas na <body>
szerokości
width na blokach
grid-template-columns: 25% 50% 25%
bloki
<section> z id
<main>, <aside>, <section> z id po treści
kolory
wpisane w reguły
zmienne CSS
zapytania
bez sortowania i aliasów, INT UNSIGNED
ORDER BY, aliasy, TINYINT UNSIGNED
biblioteka
mysqli (tabela 1 arkusza)
PDO z wyjątkami
gdzie są zapytania
Skrypt 1 w liście, Skrypt 2 w galerii
oba na górze wycieczki.php
wypisywanie
$row[0], dane z bazy wprost
pola po nazwach, e(), <time>
brak wycieczek, awaria bazy
pusta lista, ostrzeżenia PHP
komunikaty
Dane logowania do bazy w prawdziwym projekcie trzyma się zwykle w osobnym pliku (np. config.php), ale klucz sprawdza skrypt w kodzie strony - tu cały kod PHP zostaje w wycieczki.php.
Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i łatwiej ją napisać bez dokumentacji. Nowoczesna rozdziela pobieranie danych od HTML-u, więc łatwiej ją czytać i poprawiać. Obie biblioteki klucz przyjmuje.
Krok 33 · po tym etapie masz 32 z 32 kryteriów (pkt)
Krok 33: Plik przeglądarka.txt i nagranie płyty
ok. 5 min
Polecenie z arkusza
Po zakończeniu pracy utwórz plik tekstowy. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Nazwij plik przeglądarka.txt i zapisz go w folderze z numerem zdającego. Nagraj płytę z rezultatami pracy. W folderze z numerem zdającego powinny się znajdować następujące pliki: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg, 6.jpg, 7.jpg, 8.jpg, 9.jpg, import.png, kw1.jpg, kw2.jpg, kw3.jpg, kw4.jpg, kwerendy.txt, przeglądarka.txt, styl4.css, wycieczki.php ewentualnie inne przygotowane pliki.
W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była strona przez http://localhost/.... 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.