INF.03-04-24.01-SGStyczeń 2024 · 35 kroków · ok. 73 min · 33 kryteria z klucza
Rozwiązanie krok po kroku: Kalendarz z bazą danych (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza: baza terminarz z czterema zapytaniami, logo przeskalowane do 140 px, strona kalendarz.php z dwoma blokami banera i kalendarzem lipca, style w styl6.css i strona sierpien.html. Obie wersje spełniają wszystkie 33 kryteria klucza - różni je sposób pisania kodu, nie wynik.
Czym ta wersja różni się od podstawowej:
SQL czytelnie: każda klauzula w osobnej linii, zapytanie 2 filtruje po dacie (MONTH(dataZadania) = 7) zamiast po polu tekstowym z nazwą miesiąca i sortuje dni (ORDER BY),
układ na siatce CSS (display: grid na <body>) zamiast float i clear, bloki dni na flexboksie z zawijaniem, kolory w zmiennych CSS,
PHP na PDO (klucz przyjmuje MySQLi albo PDO): połączenie i oba zapytania na początku pliku kalendarz.php, w try/catch, połączenie zamknięte, zanim zacznie się HTML; w treści strony już tylko implode i pętla foreach,
każdy tekst z bazy przechodzi przez funkcję e() (htmlspecialchars), data dnia stoi w znaczniku <time>, a pełny wpis jest w dymku bloku, gdy overflow: hidden go ucina.
Baza i logo pochodzą z prawdziwego archiwum materialy4 (załącznik do arkusza, hasło w treści zadania). Zapytania są policzone na MariaDB 10.4 - tej samej, która jest w XAMPP. Podgląd strony to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2 - na bazie po zapytaniu 4, tak jak na obrazie 2 (18 lipca „Wycieczka nad morze”). Sprawdzony jest też komunikat przy niedziałającej bazie.
Jak liczą się punkty. Klucz ma 33 kryteria (R.1.1 - R.5.7), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 25 punktów. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.
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 33 kryteriów (pkt)
Krok 1: Baza danych terminarz
Kryteria:
R.1.1 Wykonano import tabel do bazy danych terminarz czynność udokumentowano plikiem o nazwie import w formacie PNG
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie terminarz.
Rozpakuj materialy4.7z z pulpitu (hasło kaL(end)arz%) do folderu z numerem zdającego: baza.sql, logo1.png i logo2.png. W phpMyAdmin: Nowa, nazwa terminarz, porównywanie np. utf8mb4_polish_ci.
Tabela z baza.sql ma zestaw znaków zapisany we własnej definicji (latin1), więc wybór dla bazy jej nie zmienia - dane i tak nie zawierają polskich liter (miesiąc jest zapisany jako „sierpien”).
Krok 2: Import tabeli z pliku baza.sql
Kryteria:
R.1.1 Wykonano import tabel do bazy danych terminarz czynność udokumentowano plikiem o nazwie import w formacie PNG
ok. 2 min
Polecenie z arkusza
Do bazy terminarz zaimportuj tabele z pliku baza.sql z rozpakowanego archiwum.
Zaznaczona baza terminarz, Import → baza.sql → Wykonaj. Powstaje tabela zadania z obrazu 1: 63 dni lipca i sierpnia 2020 z datą (dataZadania, typ DATE), wpisem (często pustym), nazwą miesiąca i rokiem.
Krok 3: Zrzut ekranu import.png
Kryteria:
R.1.1 Wykonano import tabel do bazy danych terminarz 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.
Print Screen i zapis jako import.png - import w PNG, a zrzuty zapytań w JPEG (kw1.jpg - kw4.jpg).
Typowe błędy
Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany.
Krok 4: Zapytanie 1: wpisy z pierwszego tygodnia lipca
Kryteria: ,
R.1.2 Zapisano plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Wykonano zapytanie 1 wybierające jedynie niepowtarzające się wartości z pola wpis dla zadań z pierwszych 7 dni lipca oraz takich, gdzie pole wpis nie jest puste. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT DISTINCT wpis FROM zadania WHERE dataZadania <= '2020-07-07' AND wpis <> ''; (możliwe również wpis != '', dodatkowo AND dataZadania >= '2020-07-01') oraz wynik działania udokumentowano zrzutem ekranu, na którym są widoczne jedynie dwie dane: Projekt z programowania oraz Weterynarz - Brutus
ok. 3 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie niepowtarzające się wartości z pola wpis dla zadań z pierwszych 7 dni lipca roku 2020 (od 1 do 7 lipca) oraz takich, gdzie pole wpis nie jest puste.
Każda klauzula w osobnej linii - tak czyta się dłuższe zapytania:
SELECT DISTINCT wpis - bez powtórzeń: „Projekt z programowania” jest 1 i 2 lipca, w wyniku raz,
WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' - przedział dat z oboma końcami; pole ma typ DATE, więc porównanie idzie po datach, nie po napisach,
AND wpis <> '' - bez pustych wpisów. Wartość NULL też odpada, bo porównanie z NULL nigdy nie jest prawdziwe.
Wynik: dwa wpisy, „Projekt z programowania” i „Weterynarz - Brutus” (R.1.3). Zapytanie do kwerendy.txt, zrzut kw1.jpg razem z treścią zapytania. To zapytanie wyśle Skrypt 1.
Typowe błędy
Bez wpis <> '' w wyniku pojawi się pusty wiersz - pięć pustych dni zlewa się przez DISTINCT w jeden.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT DISTINCT wpisFROM zadaniaWHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> '';
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: zadania z lipca w kolejności dni
Kryteria:
R.1.4 Wykonano zapytanie 2 wybierające jedynie pola dataZadania i wpis dla zadań z miesiąca lipca. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT dataZadania, wpis FROM zadania WHERE miesiac='lipiec'; (poprawne też dataZadania LIKE "____-07-__" lub użyto %) oraz wynik działania udokumentowano zrzutem, na którym widoczny jest w komunikacie zapis o liczbie rekordów "(31 ogółem...)", rekordy są jedynie z lipca, jedynie pola dataZadania i wpis. Zrzut nie musi zawierać wszystkich zwróconych zapytaniem wierszy
ok. 3 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie pola dataZadania i wpis dla zadań z miesiąca lipca.
WHERE MONTH(dataZadania) = 7 - funkcja MONTH wyciąga z daty numer miesiąca. Tabela ma też tekstowe pole miesiac, ale to kopia informacji, którą niesie już data - i to kopia, w której można się pomylić (sierpień jest zapisany bez „ń”). Data jest źródłem prawdy. Warunek nie wpisuje roku, więc działa dla lipca każdego roku - klucz wymaga zapytań „uniwersalnych dla każdego zestawu danych”,
ORDER BY dataZadania - kolejność dni. Bez tej klauzuli baza oddaje wiersze w dowolnej kolejności (zwykle w kolejności zapisu, ale niczego to nie gwarantuje), a kalendarz musi iść od 1 do 31 lipca.
Wynik: 31 wierszy, komunikat „(31 ogółem)” (R.1.4). Zrzut kw2.jpg nie musi mieścić wszystkich wierszy. To zapytanie wyśle Skrypt 2.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT DISTINCT wpisFROM zadaniaWHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> '';-- Zapytanie 2SELECT dataZadania, wpisFROM zadaniaWHERE MONTH(dataZadania) = 7ORDER BY dataZadania;
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: wpisy na literę S
Kryteria:
R.1.5 Wykonano zapytanie 3 wybierające jedynie pole miesiac i wpis dla wpisów zaczynających się na literę „S”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT miesiac, wpis FROM zadania WHERE wpis LIKE "S%"; oraz wynik działania udokumentowano zrzutem, na którym widoczne jest jedynie: lipiec Szczepienie, sierpien Sopot, sierpien Sopot
ok. 2 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie pole miesiac i wpis dla wpisów zaczynających się na literę „S”.
LIKE 'S%' - napis zaczynający się od „S”, po nim cokolwiek (%). Wielkość liter nie ma znaczenia przy porównywaniu latin1_swedish_ci tej tabeli, więc „s” na początku też by pasowało - w danych takich wpisów nie ma.
Wynik: trzy wiersze - lipiec „Szczepienie” i dwa razy sierpień „Sopot” (R.1.5). Zrzut kw3.jpg.
Typowe błędy
LIKE '%S%' szuka litery S w dowolnym miejscu - nie tylko na początku wpisu.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT DISTINCT wpisFROM zadaniaWHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> '';-- Zapytanie 2SELECT dataZadania, wpisFROM zadaniaWHERE MONTH(dataZadania) = 7ORDER BY dataZadania;-- Zapytanie 3SELECT miesiac, wpisFROM zadaniaWHERE wpis LIKE 'S%';
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: nowy wpis na 18 lipca
Kryteria:
R.1.6 Wykonano zapytanie 4 aktualizujące pole wpis dla zadania w dniu 2020-07-18, nowy wpis to „Wycieczka nad morze”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: UPDATE zadania SET wpis='Wycieczka nad morze' WHERE dataZadania = '2020-07-18'; oraz wynik działania udokumentowano zrzutem, na którym widoczny jest komunikat o poprawnej aktualizacji lub tabela ze zaktualizowanym wpisem
ok. 2 min
Polecenie z arkusza
Zapytanie 4: aktualizujące pole wpis dla zadania w dniu 2020-07-18, nowy wpis to „Wycieczka nad morze”.
UPDATE zadania SET wpis = 'Wycieczka nad morze' - nowa wartość pola,
WHERE dataZadania = '2020-07-18' - tylko wiersz tego dnia; wcześniej był tam wpis „Wyjazd na wakacje!”.
phpMyAdmin pokazuje „1 wiersz zmieniony” (R.1.6). Zrzut kw4.jpg. Od tej chwili kalendarz pokazuje 18 lipca nowy wpis.
Typowe błędy
UPDATE bez WHERE wpisuje „Wycieczka nad morze” we wszystkich 63 dniach.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT DISTINCT wpisFROM zadaniaWHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> '';-- Zapytanie 2SELECT dataZadania, wpisFROM zadaniaWHERE MONTH(dataZadania) = 7ORDER BY dataZadania;-- Zapytanie 3SELECT miesiac, wpisFROM zadaniaWHERE wpis LIKE 'S%';-- Zapytanie 4UPDATE zadaniaSET wpis = 'Wycieczka nad morze'WHERE dataZadania = '2020-07-18';
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 8-17 · po tym etapie masz 15 z 33 kryteriów (pkt)
Krok 8: Logo: wysokość 140 px
Kryteria:
R.2.1 Plik logo1.png przeskalowano z zachowaniem proporcji do wysokości 140 px odpowiadająca temu szerokość wynosi 175 px (± 5 px)
ok. 2 min
Polecenie z arkusza
Plik logo1.png, wypakowany z archiwum, należy przeskalować z zachowaniem proporcji tak, aby jego wysokość wynosiła dokładnie 140 px.
W GIMP-ie: Obraz → Skaluj obraz, wysokość 140 z zamkniętym łańcuchem - szerokość wychodzi 175 px (350 × 140 / 280), jak w kluczu (R.2.1). Plik → Nadpisz logo1.png - plik zostaje PNG z przezroczystością.
Podgląd strony po tym kroku
Krok 9: Plik kalendarz.php w HTML5
Kryteria:
R.2.2 Witrynę internetową zapisano w pliku kalendarz.php oraz zastosowano deklarację języka HTML5 <!DOCTYPE html>, zadeklarowano język witryny polski np. <html lang = "pl"> oraz zapisano jawnie właściwy standard kodowania polskich znaków
ok. 1 min
Polecenie z arkusza
Cechy witryny: Składa się ze strony o nazwie kalendarz.php zapisanej w języku HTML 5.
Jedna strona, kalendarz.php. Dopóki nie ma w niej kodu PHP, podgląd pokazuje ją wprost.
R.2.2 Witrynę internetową zapisano w pliku kalendarz.php oraz zastosowano deklarację języka HTML5 <!DOCTYPE html>, zadeklarowano język witryny polski np. <html lang = "pl"> oraz zapisano jawnie właściwy standard kodowania polskich znaków
Krok 11: Kodowanie polskich znaków i widok na telefonie
Kryteria:
R.2.2 Witrynę internetową zapisano w pliku kalendarz.php oraz zastosowano deklarację języka HTML5 <!DOCTYPE html>, zadeklarowano język witryny polski np. <html lang = "pl"> oraz zapisano jawnie właściwy standard kodowania polskich znaków
ok. 1 min
Polecenie z arkusza
Jawnie zastosowany właściwy standard kodowania polskich znaków.
<meta charset="UTF-8"> jako pierwszy element <head> (R.2.2), a pod nim <meta name="viewport"> - telefon pokaże stronę w swojej szerokości. Arkusz tej drugiej linii nie wymaga.
Tytuł strony widoczny na karcie przeglądarki: „Zadania na lipiec”.
<title> w <head>.
kalendarz.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Zadania na lipiec</title></head><body></body></html>
Podgląd strony po tym kroku (kalendarz.php)
Krok 13: Podpięcie arkusza stylów
Kryteria:
R.3.3 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl6.css , formatowanie CSS 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 styl6.css prawidłowo połączony z kodem strony.
Nietypowa nazwa: styl6.css - z cyfrą.
Typowe błędy
styl.css bez szóstki odcina wszystkie style, a razem z nimi R.3.3.
kalendarz.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Zadania na lipiec</title> <linkrel="stylesheet"href="styl6.css"></head><body></body></html>
Podgląd strony po tym kroku (kalendarz.php)
Krok 14: Bloki strony
Kryteria:
R.2.4 Układ strony zdefiniowano dzieląc ją na: 2 bloki banera, blok główny i stopkę. Zastosowano znaczniki sekcji
ok. 2 min
Polecenie z arkusza
Podział strony na bloki: dwa bloki banera, poniżej blok główny, w którym znajdują się generowane skryptem bloki kalendarza, na dole blok stopki. 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.
dwa bloki banera to <section> z dwiema klasami: wspólną banner (te same kolory i wysokość) i własną (banner-logo, banner-title) - element może mieć kilka klas oddzielonych spacją,
<main class="calendar"> na blok główny i <footer> (R.2.4).
Bloki dni nie są tu wpisane - wygeneruje je Skrypt 2.
R.2.5 W banerze drugim zapisano nagłówek h1: "TERMINARZ"
R.2.6 W banerze drugim zapisano paragraf rozpoczynający się od: "najbliższe zadania:" oraz w stopce zapisano paragraf z numerem zdającego
ok. 1 min
Polecenie z arkusza
Zawartość drugiego bloku banera: Nagłówek pierwszego stopnia o treści: „TERMINARZ”. Paragraf (akapit) z wpisem: „najbliższe zadania:”, a dalej efekt działania skryptu 1.
<h1> i <p> (R.2.5, R.2.6). Akapit kończy się po dwukropku i spacji - zadania dopisze Skrypt 1.
R.2.6 W banerze drugim zapisano paragraf rozpoczynający się od: "najbliższe zadania:" oraz w stopce zapisano paragraf z numerem zdającego
R.3.2 Wybranie odnośnika "Terminarz na sierpień" powoduje otwarcie strony sierpien.html
ok. 2 min
Polecenie z arkusza
Zawartość stopki: Odnośnik o treści „Terminarz na sierpień” prowadzący do pliku sierpien.html. Plik sierpien.html należy utworzyć i zapisać w nim „strona w trakcie budowy”. Paragraf (akapit) o treści: „Stronę wykonał: ”, dalej wstawiony numer zdającego.
Odnośnik do pliku z tego samego folderu i nowy plik sierpien.html z napisem w krótkim szkielecie HTML5 (R.3.2). Na egzaminie po „Stronę wykonał:” wpisujesz swój numer zdającego (PESEL); tu stoi autor rozwiązania (R.2.6).
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Terminarz na sierpień</title></head><body> <p>strona w trakcie budowy</p></body></html>
Podgląd strony po tym kroku (kalendarz.php)
Etap 3: Styl CSS
Kroki 18-25 · po tym etapie masz 26 z 33 kryteriów (pkt)
Krok 18: Plik styl6.css: kolory z arkusza w zmiennych
ok. 2 min
Polecenie z arkusza
Styl CSS zdefiniowany w całości w zewnętrznym pliku o nazwie styl6.css.
Kolor #6F79A8 pojawia się w arkuszu pięć razy (banery, stopka, ramka dnia, dzień pod kursorem). Zapisz kolory raz, jako zmienne CSS w :root, i odwołuj się do nich przez var(--nazwa). Sama definicja nic nie zmienia na stronie.
Krok 21: Szerokości banerów i układ strony na siatce
Kryteria: ,
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu (Nie należy sprawdzać bloku głównego i bloków kalendarza; prawidłowo zastosowano właściwości CSS decydujące o układzie strony, np. float i clear albo display flex albo grid albo position)
R.4.4 Ustawiono szerokość bloków banera: 30% pierwszy oraz 70% drugi (width lub grid- template-columns lub flex)
ok. 3 min
Polecenie z arkusza
Dodatkowo szerokość pierwszego bloku banera wynosi 30%, drugiego 70%.
<body> staje się siatką o kolumnach 30% i 70% - szerokości banerów z arkusza, zapisane w grid-template-columns (klucz to przyjmuje, R.4.4).
grid-template-areas rysuje układ obrazu 2: w pierwszym wierszu logo i tytuł, pod nimi kalendarz i stopka przez obie kolumny. W wersji podstawowej ten sam układ dają float: left na banerach i clear: both w bloku głównym - tu nie ma opływania do czyszczenia (R.3.1).
Typowe błędy
Szerokości banerów wpisuj tylko w grid-template-columns. Dodatkowe width: 30% na bloku liczyłoby się od kolumny siatki - blok miałby 9% strony.
R.4.8 Ustawiono dla bloku kalendarza: kolor tła #9FA8DA, szerokość 160px, wysokość 85px, marginesy zewnętrzne (margin) 5px, obramowanie 1px solid #6F79A8 (np. za pomocą klasy. Użycie id jest błędem)
R.4.9 Ustawiono dla bloku kalendarza ukrycie tekstu, który nie mieści się w bloku bez paska przewijania (overflow: hidden;)
ok. 3 min
Polecenie z arkusza
Dla bloku kalendarza: kolor tła #9FA8DA, szerokość 160 px, wysokość 85 px, marginesy zewnętrzne 5 px, obramowanie linią ciągłą o szerokości 1 px i kolorze #6F79A8; tekst, który nie mieści się w bloku jest ukryty i wyświetlany bez paska przewijania (np. obraz 2, pierwszy blok kalendarza).
bloków dnia jest 31, więc styl dostaje klasa.day - identyfikator musi być na stronie jednorazowy (klucz: użycie id jest błędem, R.4.8),
overflow: hidden obcina tekst, który nie mieści się w 85 px, bez paska przewijania (R.4.9),
blok główny .calendar układa dni flexboksem z zawijaniem (flex-wrap: wrap): dni stoją w wierszu, a gdy brak miejsca, przechodzą do następnego - tak jak na obrazie 2. Marginesy 5 px w flexboksie się nie sumują ani nie łączą, więc między dniami jest 10 px odstępu.
Typowe błędy
#day zamiast .day - klucz wprost uznaje identyfikator za błąd (R.4.8).
Kroki 26-32 · po tym etapie masz 33 z 33 kryteriów (pkt)
Krok 26: Połączenie PDO na początku strony i zamknięcie
Kryteria: ,
R.5.1 Skrypt zawiera działające połączenie z serwerem bazy danych i wybór bazy terminarz
R.5.2 Ostatnią operacją na bazie jest jej zamknięcie
ok. 4 min
Polecenie z arkusza
Napisany w języku PHP, w pliku kalendarz.php. Łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie terminarz. Na końcu działania skrypt zamyka połączenie z serwerem.
Cały dostęp do bazy stoi na początku pliku, przed <!DOCTYPE html>: najpierw dane, potem strona. Klucz przyjmuje bibliotekę MySQLi albo PDO - tu PDO:
new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [...]) - serwer, baza terminarz i kodowanie w jednym napisie (DSN), potem użytkownik i hasło (R.5.1),
ERRMODE_EXCEPTION - każdy błąd bazy rzuca wyjątek, zamiast cicho zwrócić false; FETCH_ASSOC - wiersze jako tablice z nazwami kolumn ($day['wpis'] zamiast $row[1]),
$pdo = null zamyka połączenie. Między połączeniem a tą linią staną oba zapytania, więc zamknięcie będzie ostatnią operacją na bazie (R.5.2),
try/catch: gdy serwer nie działa albo baza nie istnieje, użytkownik zobaczy zrozumiały komunikat z $error, a nie treść wyjątku z nazwą serwera i użytkownika. Od PHP 8 catch (PDOException) nie musi nazywać zmiennej, gdy treść wyjątku nie jest potrzebna.
Podgląd pokazuje wynik uruchomienia strony - skrypty jeszcze niczego nie wypisują.
kalendarz.phpPHPzmienione: 12 linii
<?phptry {$pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$pdo = null;} catch (PDOException) {$error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Zadania na lipiec</title> <linkrel="stylesheet"href="styl6.css"></head><body> <sectionclass="banner banner-logo"> <imgsrc="logo1.png"alt="lipiec"width="175"height="140"> </section> <sectionclass="banner banner-title"> <h1>TERMINARZ</h1> <p>najbliższe zadania: </p> </section> <mainclass="calendar"> </main> <footer> <ahref="sierpien.html">Terminarz na sierpień</a> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (kalendarz.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. 3 min
Polecenie z arkusza
Działanie skryptu 1: Wysyła do bazy danych zapytanie 1.
$pdo->query(...) wysyła zapytanie 1 (R.5.3). W zapytaniu nie ma żadnej wartości od użytkownika, więc nie trzeba go przygotowywać (prepare) - przygotowanie chroni przed wstrzyknięciem danych, których tu nie ma,
fetchAll(PDO::FETCH_COLUMN) oddaje od razu zwykłą listę wartości jednej kolumny: ['Projekt z programowania', 'Weterynarz - Brutus'],
$upcoming = [] przed try - gdy baza zawiedzie, lista jest pusta, a strona i tak się wyświetli.
kalendarz.phpPHPzmienione: 6 linii
<?php$upcoming = [];try {$pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''") ->fetchAll(PDO::FETCH_COLUMN);$pdo = null;} catch (PDOException) {$error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Zadania na lipiec</title> <linkrel="stylesheet"href="styl6.css"></head><body> <sectionclass="banner banner-logo"> <imgsrc="logo1.png"alt="lipiec"width="175"height="140"> </section> <sectionclass="banner banner-title"> <h1>TERMINARZ</h1> <p>najbliższe zadania: </p> </section> <mainclass="calendar"> </main> <footer> <ahref="sierpien.html">Terminarz na sierpień</a> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
R.5.4 Skrypt 1 wyświetla rozdzielone średnikiem i spacją wszystkie wartości zwrócone zapytaniem
ok. 3 min
Polecenie z arkusza
Wyświetla rozdzielone średnikiem i spacją wszystkie wartości zwrócone zapytaniem.
implode('; ', $upcoming) skleja wszystkie wpisy z listy, wstawiając średnik i spację między nimi (R.5.4),
<?= ... ?> to skrót od <?php echo ... ?> - wygodny w treści HTML,
funkcja e() (od escape) przepuszcza tekst przez htmlspecialchars: znaki <, >, & i cudzysłowy zamienia na encje, więc wpis z kalendarza zawsze wyświetli się jako tekst, a nie kawałek HTML. ?string i ?? '' pozwalają przekazać też NULL z bazy.
Podgląd: „najbliższe zadania: Projekt z programowania; Weterynarz - Brutus”. Na obrazie 2 po ostatnim wpisie stoi jeszcze średnik - tak wypisuje pętla z echo "...; ". Klucz wymaga wartości rozdzielonych średnikiem, więc obie formy są poprawne.
kalendarz.phpPHPzmienione: 6 linii
<?php$upcoming = [];try {$pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''") ->fetchAll(PDO::FETCH_COLUMN);$pdo = null;} catch (PDOException) {$error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';}functione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Zadania na lipiec</title> <linkrel="stylesheet"href="styl6.css"></head><body> <sectionclass="banner banner-logo"> <imgsrc="logo1.png"alt="lipiec"width="175"height="140"> </section> <sectionclass="banner banner-title"> <h1>TERMINARZ</h1> <p>najbliższe zadania: <?= e(implode('; ', $upcoming)) ?></p> </section> <mainclass="calendar"> </main> <footer> <ahref="sierpien.html">Terminarz na sierpień</a> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (kalendarz.php)
Krok 29: Skrypt 2: zapytanie 2
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
Działanie skryptu 2: Wysyła do bazy danych zapytanie 2.
Drugie zapytanie na tym samym połączeniu, przed jego zamknięciem (R.5.3). fetchAll() oddaje listę 31 wierszy, każdy z kluczami dataZadania i wpis, już posortowaną po dacie. Lista $days też startuje pusta przed try.
Na stronie jeszcze nic się nie zmienia - dni wypisze pętla w bloku głównym.
kalendarz.phpPHPzmienione: 5 linii
<?php$upcoming = [];$days = [];try {$pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''") ->fetchAll(PDO::FETCH_COLUMN);$days = $pdo->query("SELECT dataZadania, wpis FROM zadania WHERE MONTH(dataZadania) = 7 ORDER BY dataZadania") ->fetchAll();$pdo = null;} catch (PDOException) {$error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';}functione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Zadania na lipiec</title> <linkrel="stylesheet"href="styl6.css"></head><body> <sectionclass="banner banner-logo"> <imgsrc="logo1.png"alt="lipiec"width="175"height="140"> </section> <sectionclass="banner banner-title"> <h1>TERMINARZ</h1> <p>najbliższe zadania: <?= e(implode('; ', $upcoming)) ?></p> </section> <mainclass="calendar"> </main> <footer> <ahref="sierpien.html">Terminarz na sierpień</a> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
R.5.5 Skrypt 2 wyświetla w bloku głównym bloki zdefiniowane za pomocą znaczników sekcji. Liczba wygenerowanych bloków odpowiada liczbie wierszy zwróconych zapytaniem
ok. 4 min
Polecenie z arkusza
Za pomocą znacznika sekcji definiuje blok, a w nim wyświetla dane dotyczące jednego zwróconego wiersza. Data zadania w nagłówku szóstego stopnia. Liczba wygenerowanych bloków odpowiada liczbie wierszy zwróconych zapytaniem.
Pętla foreach ($days as $day): ... endforeach; - zapis z dwukropkiem jest przeznaczony do szablonów: kod HTML bloku stoi zwykłym tekstem między dwoma krótkimi wstawkami PHP, bez sklejania znaczników w napisach i bez ukośników przed cudzysłowami.
Każdy wiersz daje jeden <section class="day"> (R.5.5) z datą w <h6>. Data jest dodatkowo w znaczniku <time datetime="..."> - przeglądarki, wyszukiwarki i czytniki ekranu wiedzą wtedy, że to data, a nie zwykły tekst. Podgląd: 31 bloków, tyle, ile wierszy zwraca zapytanie.
Typowe błędy
Jeden <section> wypisany przed pętlą, a w pętli tylko daty - wtedy jest jeden blok zamiast 31 (R.5.5).
kalendarz.phpPHPzmienione: 5 linii
<?php$upcoming = [];$days = [];try {$pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''") ->fetchAll(PDO::FETCH_COLUMN);$days = $pdo->query("SELECT dataZadania, wpis FROM zadania WHERE MONTH(dataZadania) = 7 ORDER BY dataZadania") ->fetchAll();$pdo = null;} catch (PDOException) {$error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';}functione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Zadania na lipiec</title> <linkrel="stylesheet"href="styl6.css"></head><body> <sectionclass="banner banner-logo"> <imgsrc="logo1.png"alt="lipiec"width="175"height="140"> </section> <sectionclass="banner banner-title"> <h1>TERMINARZ</h1> <p>najbliższe zadania: <?= e(implode('; ', $upcoming)) ?></p> </section> <mainclass="calendar"><?phpforeach ($daysas$day): ?> <sectionclass="day"> <h6><timedatetime="<?= e($day['dataZadania']) ?>"><?= e($day['dataZadania']) ?></time></h6> </section><?phpendforeach; ?> </main> <footer> <ahref="sierpien.html">Terminarz na sierpień</a> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (kalendarz.php)
Krok 31: Skrypt 2: wpis w akapicie
Kryteria: ,
R.5.6 Skrypt 2 wyświetla datę zadania w nagłówku h6 oraz pole wpis w paragrafie
R.5.7 Zastosowano znaczące nazewnictwo dla wszystkich zmiennych lub funkcji w języku polskim lub angielskim. Nazwy odzwierciedlają cel zastosowania
ok. 3 min
Polecenie z arkusza
Pole wpis w paragrafie (akapicie).
Pod datą akapit z wpisem (R.5.6), jak zawsze przez e(). Ten sam wpis trafia do atrybutu title bloku: overflow: hidden ucina długie wpisy („Wycieczka nad”), a po najechaniu przeglądarka pokaże całość w dymku. e() z ENT_QUOTES zamienia też cudzysłowy - wpis z cudzysłowem nie zamknie atrybutu przedwcześnie.
Podgląd jak na obrazie 2: wpisy w blokach, 18 lipca ucięte „Wycieczka nad”. Najedź na dzień - zmieni kolor. Nazwy mówią, co przechowują: $upcoming, $days, $day, e() (R.5.7).
kalendarz.phpPHPzmienione: 2 linie
<?php$upcoming = [];$days = [];try {$pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''") ->fetchAll(PDO::FETCH_COLUMN);$days = $pdo->query("SELECT dataZadania, wpis FROM zadania WHERE MONTH(dataZadania) = 7 ORDER BY dataZadania") ->fetchAll();$pdo = null;} catch (PDOException) {$error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';}functione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Zadania na lipiec</title> <linkrel="stylesheet"href="styl6.css"></head><body> <sectionclass="banner banner-logo"> <imgsrc="logo1.png"alt="lipiec"width="175"height="140"> </section> <sectionclass="banner banner-title"> <h1>TERMINARZ</h1> <p>najbliższe zadania: <?= e(implode('; ', $upcoming)) ?></p> </section> <mainclass="calendar"><?phpforeach ($daysas$day): ?> <sectionclass="day"title="<?= e($day['wpis']) ?>"> <h6><timedatetime="<?= e($day['dataZadania']) ?>"><?= e($day['dataZadania']) ?></time></h6> <p><?= e($day['wpis']) ?></p> </section><?phpendforeach; ?> </main> <footer> <ahref="sierpien.html">Terminarz na sierpień</a> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (kalendarz.php)
Krok 32: Komunikat, gdy baza nie działa
ok. 2 min
Polecenie z arkusza
Zawartość bloku głównego: efekt działania skryptu 2.
Gdy połączenie albo zapytanie się nie uda, catch z pierwszego kroku ustawia $error. Blok główny wyświetla wtedy ten komunikat zamiast pustego kalendarza: isset($error) sprawdza, czy zmienna w ogóle powstała. Klasa .error zajmuje cały wiersz flexboksu (flex-basis: 100%) i ma ciemnoczerwony kolor.
Sprawdzone z błędną nazwą bazy: strona wyświetla banery, stopkę i komunikat, bez ostrzeżeń PHP. Klucz tego nie ocenia.
kalendarz.phpPHPzmienione: 3 linie
<?php$upcoming = [];$days = [];try {$pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''") ->fetchAll(PDO::FETCH_COLUMN);$days = $pdo->query("SELECT dataZadania, wpis FROM zadania WHERE MONTH(dataZadania) = 7 ORDER BY dataZadania") ->fetchAll();$pdo = null;} catch (PDOException) {$error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';}functione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Zadania na lipiec</title> <linkrel="stylesheet"href="styl6.css"></head><body> <sectionclass="banner banner-logo"> <imgsrc="logo1.png"alt="lipiec"width="175"height="140"> </section> <sectionclass="banner banner-title"> <h1>TERMINARZ</h1> <p>najbliższe zadania: <?= e(implode('; ', $upcoming)) ?></p> </section> <mainclass="calendar"><?phpif (isset($error)): ?> <pclass="error"><?= e($error) ?></p><?phpendif; ?><?phpforeach ($daysas$day): ?> <sectionclass="day"title="<?= e($day['wpis']) ?>"> <h6><timedatetime="<?= e($day['dataZadania']) ?>"><?= e($day['dataZadania']) ?></time></h6> <p><?= e($day['wpis']) ?></p> </section><?phpendforeach; ?> </main> <footer> <ahref="sierpien.html">Terminarz na sierpień</a> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Kroki 33-34 · po tym etapie masz 33 z 33 kryteriów (pkt)
Krok 33: Co jest inaczej i dlaczego
ok. 3 min
Polecenie z arkusza
Ocenie będzie podlegać 5 rezultatów: operacje na bazie danych, zawartość witryny internetowej, działanie witryny internetowej, styl CSS witryny internetowej, skrypt połączenia z bazą.
Obie wersje dają te same punkty. Różnice:
wersja podstawowa
ta wersja
zapytanie 2
WHERE miesiac = 'lipiec'
WHERE MONTH(dataZadania) = 7 ORDER BY dataZadania
układ
float: left + clear: both
siatka na <body>, dni na flexboksie
banery
dwa identyfikatory
wspólna klasa banner + własne klasy
kolory
wpisane w każdej regule
zmienne w :root
biblioteka
mysqli_* w dwóch blokach PHP w treści
PDO na początku pliku, w HTML-u tylko wyświetlanie
błąd bazy
ostrzeżenia PHP na stronie
try/catch i komunikat dla użytkownika
Skrypt 1
pętla z echo "...; " (średnik też na końcu)
implode('; ', ...) (średniki tylko między wpisami)
Skrypt 2
blok sklejany w napisie z \"
szablon foreach: ... endforeach;
tekst z bazy
wypisywany wprost
przez e() (htmlspecialchars), pełny wpis w title
Sprawdzone: podgląd każdego kroku to prawdziwe uruchomienie w PHP 8.2 na MariaDB, układ zmierzony w Chrome (banery 30% i 70% w jednym wierszu, 31 dni po 162 × 87 px z ramką, stopka pod ostatnim), awaria bazy daje komunikat bez ostrzeżeń.
Czas przeznaczony na wykonanie zadania wynosi 150 minut.
Wersja podstawowa, gdy liczy się czas: funkcje mysqli_* są w tabeli 1 arkusza, więc nie trzeba ich pamiętać, a float wystarczy do tego układu.
Ta wersja, gdy PDO i siatkę CSS pisze się z pamięci. Rozdzielenie „najpierw dane, potem strona” pomaga przy szukaniu błędów: jeśli coś jest nie tak z bazą, wszystko stoi w kilkunastu liniach na górze pliku.
Mieszanie też daje pełne punkty: siatka i zmienne z tej wersji, mysqli z podstawowej.
Krok 35 · po tym etapie masz 33 z 33 kryteriów (pkt)
Krok 35: 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 strona była sprawdzana.
W folderze: import.png, kalendarz.php, kw1.jpg - kw4.jpg, kwerendy.txt, logo1.png, przeglądarka.txt, sierpien.html, styl6.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.