Materiał do egzaminu INF.03 — skrypty PHP i praca z bazą w części praktycznej

PHP i MySQL na egzaminie INF.03

Kurs oparty na arkuszach egzaminu praktycznego INF.03 z czerwca 2026. Struktura arkusza i punktacja, XAMPP i phpMyAdmin, wzorce kwerend SQL, funkcje mysqli, parametry GET, formularze, koszyk, sesje i LEFT JOIN, a do tego bezpieczeństwo i trzy kompletne zadania z gotowymi rozwiązaniami.

21lekcji
4zadań praktycznych
45pytań „Sprawdź się”
Spis treści

W części praktycznej egzaminu INF.03 w czterech z dwunastu analizowanych arkuszy pojawiły się skrypty PHP połączone z bazą MySQL: galeria z opisem, tabela z formularzem zapisu, sklep z koszykiem i logowanie z sesją. Ten kurs rozkłada je na wzorce, które wystarczy opanować: szkielet połączenia, pętla po wynikach, parametr GET, formularz POST, sesja. Każdy wzorzec ma kompletny kod i listę typowych błędów.

Do ćwiczeń przygotowano trzy bazy z plikami (podroze.zip, sklep.zip, biblioteka.zip) – zaimportuj je w phpMyAdmin tak jak na egzaminie. Kod z lekcji przepisuj do własnych plików w katalogu htdocs i uruchamiaj przez http://localhost/. Każda lekcja kończy się pytaniami „Sprawdź się”, a każda część zadaniem praktycznym.

Część A

Egzamin, środowisko, baza danych i SQL

Struktura arkusza i punktacja, praca w XAMPP i phpMyAdmin oraz wzorce kwerend SQL spotykanych na egzaminie.

Część A, lekcja 1

Jak wygląda zadanie z PHP na egzaminie INF.03

W części praktycznej egzaminu INF.03 masz 150 minut na wykonanie jednego, kompletnego zadania: bazy danych, witryny (HTML + CSS) i skryptów. W arkuszach z czerwca 2026 skrypty po stronie serwera (PHP) miały cztery zadania: portal turystyczny (01), hobbystyczna witryna z formularzem (02), sklep z antykami i koszykiem (05) oraz portal laboratorium z logowaniem (10). W pozostałych ośmiu zadaniach skrypty pisało się w JavaScript (patrz kurs JavaScript).

Każdy arkusz ma tę samą strukturę, a punktacja dzieli się na pięć rezultatów:

RezultatCo jest ocenianeJak zdobyć punkty
R.1 Operacje na bazie danychimport bazy do phpMyAdmin (zrzut import), plik kwerendy.txt, zrzuty kw1…kw5 z wynikamipoprawne zapytania SQL i zrzuty całego ekranu z paskiem zadań, nazwane dokładnie jak w poleceniu
R.2 Zawartość witrynyHTML5, lang="pl", UTF-8, tytuł strony, bloki semantyczne, nagłówki, tabele, formularze, obrazy z altdokładne teksty i znaczniki z arkusza
R.3 Działanie witrynyukład bloków zgodny z ilustracją, połączenie z styl.css, działające odnośniki i formularzewłaściwy układ (flex, grid, float) i poprawne atrybuty
R.4 Styl CSSkolory, marginesy, rozmiary, obramowania, efekt :hoverwartości podane w arkuszu, selektory zgodne z uwagami („wyłącznie selektorem tego znacznika”)
R.5 Skryptypołączenie z bazą localhost/root/bez hasła, zapytania, generowanie HTML, zamknięcie połączeniapełne wzorce z tego kursu

Kryteria R.5 – skrypty PHP (typowe)

  • „Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy nazwa, a ostatnią operacją na bazie jest jej zamknięcie” – mysqli_connect(...) na początku i mysqli_close(...) na końcu.
  • „Skrypt wysyła do bazy zapytanie N” – w kodzie musi być zapytanie o treści zgodnej z poleceniem (np. WHERE idRodzaj = 10).
  • „Dla każdego zwróconego rekordu wyświetla…” – pętla while po wierszach wyniku i dokładnie takie elementy HTML, jakich wymaga arkusz.
  • „Zmodyfikowane zapytanie” – np. dodany WHERE z parametrem GET albo wstawienie wartości z formularza.
  • Kryteria 5.1–5.3 i 5.5 sprawdza się w kodzie źródłowym; kryteria działania (5.4, 5.6, 5.7) po uruchomieniu (XAMPP 8.2) – gdy skrypt generuje błąd, praca jest oceniana z jego pominięciem.
  • Dopuszczalne biblioteki: MySQLi i PDO. Arkusz zawiera tabelę z funkcjami mysqli_*, dlatego w tym kursie piszemy w stylu proceduralnym mysqli.
Zadanie z arkuszaWzorzec skryptówLekcja
portal turystyczny (01)galeria miniatur z bazy (odnośniki z parametrem GET), strona szczegółów, dwie listy numerowane z zapytań7, 8, 15
witryna hobbystyczna (02)odnośniki kontynentów, tabela filtrowana parametrem GET (z wartością domyślną), lista rozwijana, formularz z INSERT7, 8, 9
sklep z antykami (05)trzy warianty tego samego skryptu (kategorie), przycisk „KUP” z value, INSERT, lista koszyka i suma10, 16
portal laboratorium (10)logowanie i sesja, ochrona strony, tabela z LEFT JOIN, link do pliku PDF, wylogowanie11, 12, 17
Zasada czasu

Orientacyjny podział 150 minut: baza i kwerendy ok. 25 min, struktura HTML ok. 20 min, CSS ok. 30 min, skrypty PHP/JS ok. 40 min, zrzuty, test w przeglądarce i porządki 15 min, zapas 20 min. Kwerendy SQL i HTML to najpewniejsze punkty – wykonaj je przed skryptami.

Sprawdź się — zadanie 1

Jakie dwie operacje na bazie muszą wykonać skrypty PHP na początku i na końcu?

Pokaż przykładową odpowiedź

Na początku połączenie z serwerem i wybór bazy (mysqli_connect), a na końcu zamknięcie połączenia (mysqli_close).

Sprawdź się — zadanie 2

Co zrobić, gdy skrypt PHP generuje błąd, a nie ma czasu go naprawić?

Pokaż przykładową odpowiedź

Zostawić poprawny logicznie kod (komisja oceni go w źródle), a wyłączyć błędny fragment komentarzem tak, aby reszta strony działała – skrypt z błędem jest oceniany z pominięciem.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Uzupełnij otwarcie i zamknięcie bloku PHP, aby skrypt wypisał tekst na stronie.


echo "Egzamin INF.03";
Ćwiczenie interaktywne — popraw kod

Skrypt nie wypisuje tekstu „Witaj”: używa skróconego znacznika, a w instrukcji brakuje średnika. Popraw zapis tak, aby działał w XAMPP z domyślną konfiguracją.

Ćwiczenie interaktywne — uzupełnij kod

Plik ze skryptem PHP ma rozszerzenie .php i uruchamiamy go przez serwer, a nie przez dwukrotne kliknięcie. Uzupełnij nazwę pliku i adres.

Plik: index
Adres w przeglądarce: http:///portal/index.php

Część A, lekcja 2

Środowisko egzaminacyjne: XAMPP, phpMyAdmin i porządek w plikach

NarzędzieZastosowanie
XAMPP (panel Control Panel)uruchamia serwer Apache (strony i PHP) oraz MySQL/MariaDB (baza); oba moduły muszą mieć status „Running”
htdocskatalog główny serwera WWW: plik C:\xampp\htdocs\12345\index.php otwierasz w przeglądarce jako http://localhost/12345/index.php
phpMyAdmin (http://localhost/phpmyadmin)tworzenie bazy, import SQL, wykonywanie zapytań, zrzuty ekranu
edytor koduNotepad++, VS Code, Brackets – pliki w kodowaniu UTF-8
przeglądarkatestowanie; nazwę przeglądarki zapisujesz w pliku przegladarka.txt

Kolejność czynności z arkusza

  • Zaloguj się na konto Egzamin (bez hasła), rozpakuj archiwum z plikami (hasło jest w arkuszu).
  • Utwórz folder o nazwie numeru zdającego (np. na pulpicie) – tam zapisujesz wszystkie wyniki. Pliki witryny PHP muszą być uruchamiane przez serwer, więc pracę wykonuj w htdocs (w podfolderze) i na końcu skopiuj do folderu zdającego.
  • W phpMyAdmin: Nowa → nazwa bazy z arkusza (np. islandia) → zestaw znaków wskazany w arkuszu (utf8_unicode_ci lub polski, np. utf8_polish_ci) → Utwórz.
  • Wybierz bazę → zakładka Import → plik .sql z archiwum → Wykonaj. Sprawdź, czy tabele są widoczne w lewej kolumnie.
  • Zrzut ekranu po imporcie: cały ekran z paskiem zadań, widoczne tabele; zapis w formacie wskazanym (JPEG lub PNG) pod nazwą import. Nie kadruj!
  • Każdą kwerendę wykonaj w zakładce SQL, zapisz jej treść w pliku kwerendy.txt i zrób osobny zrzut wyniku (kw1, kw2 …) – widoczne ma być zapytanie i wynik.
  • Na końcu utwórz plik przegladarka.txt, zapisz kopię rezultatów na płytę/nośnik zgodnie z arkuszem i sprawdź listę plików wymaganych w folderze.
Błędy, za które traci się punkty

Przycięty zrzut ekranu (komisja nie ocenia przyciętych), zła nazwa lub format zrzutu, zapytanie zapisane inaczej niż wykonane, import do bazy o złej nazwie lub w złym zestawie znaków, pliki zapisane poza folderem zdającego.

Jak działa zapytanie w phpMyAdmin

  • Zakładka SQL pozwala wpisać zapytanie i uruchomić je przyciskiem Wykonaj; wynik pojawia się poniżej, a nad wynikiem widać treść zapytania (przydatne na zrzucie).
  • Zapytania modyfikujące (INSERT) pokazują komunikat o liczbie dodanych wierszy; do zrzutu lepiej od razu otworzyć tabelę w zakładce Przeglądaj.
  • Nazwy kolumn i tabel w arkuszu mają dokładną pisownię (idObiekt, wartoscCechy) – skopiuj je z opisu tabel na ilustracji.
Sprawdź się — zadanie 1

Gdzie w XAMPP umieszczasz pliki PHP, aby serwer je wykonał?

Pokaż przykładową odpowiedź

W katalogu htdocs (np. C:\xampp\htdocs\12345\), a otwierasz je przez http://localhost/...; otwarcie pliku z dysku nie uruchomi PHP.

Sprawdź się — zadanie 2

Co ma być widoczne na zrzucie ekranu po imporcie bazy?

Pokaż przykładową odpowiedź

Cały ekran z paskiem zadań oraz elementy świadczące o poprawnym imporcie, czyli baza o właściwej nazwie i jej tabele w phpMyAdmin.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Uzupełnij połączenie z bazą w XAMPP: serwer lokalny, użytkownik administracyjny bez hasła i baza o nazwie podroze.

$polaczenie = mysqli_connect("", "", "", "");
Ćwiczenie interaktywne — popraw kod

Argumenty funkcji mysqli_connect stoją w złej kolejności (serwer, użytkownik, hasło, baza), więc połączenie nie powiedzie się. Popraw wywołanie.

Ćwiczenie interaktywne — uzupełnij kod

Zgodnie z poleceniem utwórz w phpMyAdmin bazę o nazwie podroze ze zbiorem znaków utf8 i porównywaniem utf8_unicode_ci (zapytanie SQL).

 podroze
CHARACTER SET utf8
COLLATE ;

Część A, lekcja 3

Kwerendy SQL egzaminu: wzorce rozwiązań

Rezultat R.1 to zwykle cztery lub pięć zapytań do bazy z arkusza, a potem te same zapytania (czasem zmodyfikowane) trafiają do skryptów PHP. Ćwiczenia w tej lekcji wykonaj na bazie podroze z paczki podroze.zip (plik podroze.sql, obrazy w img). Tabele: regiony, kategorie, atrakcje, turysci, wizyty (relacja m:n między atrakcjami i turystami przez tabelę wizyt).

Struktura bazy ćwiczeniowej
regiony(idRegion, nazwa)
kategorie(idKategoria, nazwa)
atrakcje(idAtrakcja, nazwa, kraj, idRegion → regiony, idKategoria → kategorie, plik, cena, ocena, opis)
turysci(idTurysta, nick, miasto)
wizyty(idWizyta, idAtrakcja → atrakcje, idTurysta → turysci, dataWizyty)
Typ zapytania z arkuszaWzorzecPrzykład na bazie podroze
wybór kilku pól z warunkiemSELECT pola FROM tabela WHERE warunek;SELECT idAtrakcja, plik, nazwa FROM atrakcje WHERE kraj = 'Polska';
warunek na identyfikatorzeWHERE id = liczbaSELECT nazwa, cena FROM atrakcje WHERE idKategoria = 2;
dwa warunkiWHERE a AND bSELECT nazwa FROM atrakcje WHERE idRegion = 1 AND idKategoria = 1;
sortowanieORDER BY pole [DESC]SELECT nazwa, ocena FROM atrakcje ORDER BY ocena DESC;
relacja 1:n (JOIN)JOIN tabela ON a.id = b.id lub JOIN tabela USING(id)SELECT atrakcje.nazwa, kategorie.nazwa FROM atrakcje JOIN kategorie ON atrakcje.idKategoria = kategorie.idKategoria WHERE idAtrakcja = 4;
relacja m:n (dwa JOIN)przez tabelę łączącąSELECT atrakcje.nazwa, turysci.nick FROM wizyty JOIN atrakcje ON wizyty.idAtrakcja = atrakcje.idAtrakcja JOIN turysci ON wizyty.idTurysta = turysci.idTurysta;
zliczanie z grupowaniem i aliasemCOUNT(*) AS alias ... GROUP BYSELECT regiony.nazwa, COUNT(*) AS 'Liczba atrakcji' FROM atrakcje JOIN regiony USING(idRegion) GROUP BY regiony.nazwa;
wstawienie rekorduINSERT INTO t (kolumny) VALUES (...)INSERT INTO wizyty (idAtrakcja, idTurysta, dataWizyty) VALUES (5, 2, '2026-06-01');
data bez godziny, liczenie po dniachDATE(pole), GROUP BY DATE(pole)SELECT dataWizyty, COUNT(*) FROM wizyty GROUP BY dataWizyty;
relacja opcjonalna (LEFT JOIN)LEFT JOIN zwraca wszystkie wiersze lewej tabeliSELECT atrakcje.nazwa, wizyty.dataWizyty FROM atrakcje LEFT JOIN wizyty USING(idAtrakcja);

Ćwiczenia (wynik sprawdź w phpMyAdmin)

Kwerendy do samodzielnego rozwiązania
1. Pola idAtrakcja, plik i nazwa wszystkich atrakcji z Hiszpanii.
2. Pola nazwa, cena i opis atrakcji o identyfikatorze 4 oraz odpowiadająca im nazwa kategorii (relacja).
3. Nazwy atrakcji z regionu Europa (idRegion = 1), których kategoria to „Zabytek” (idKategoria = 1).
4. Nazwa kategorii oraz liczba atrakcji w danej kategorii (kolumna z aliasem „Liczba atrakcji”; grupowanie i relacja).
5. Nick i miasto turysty oraz nazwa odwiedzonej atrakcji dla wszystkich wizyt (relacja m:n).
6. Wszystkie atrakcje wraz z liczbą wizyt (także te bez wizyt, wynik 0) – LEFT JOIN i COUNT(wizyty.idWizyta).
Przykładowe rozwiązania
-- 1
SELECT idAtrakcja, plik, nazwa FROM atrakcje WHERE kraj = 'Hiszpania';
-- 2
SELECT atrakcje.nazwa, cena, opis, kategorie.nazwa AS kategoria
FROM atrakcje JOIN kategorie ON atrakcje.idKategoria = kategorie.idKategoria
WHERE idAtrakcja = 4;
-- 3
SELECT nazwa FROM atrakcje WHERE idRegion = 1 AND idKategoria = 1;
-- 4
SELECT kategorie.nazwa, COUNT(*) AS `Liczba atrakcji`
FROM atrakcje JOIN kategorie USING(idKategoria)
GROUP BY kategorie.nazwa;
-- 5
SELECT turysci.nick, turysci.miasto, atrakcje.nazwa
FROM wizyty
JOIN turysci  ON wizyty.idTurysta  = turysci.idTurysta
JOIN atrakcje ON wizyty.idAtrakcja = atrakcje.idAtrakcja;
-- 6
SELECT atrakcje.nazwa, COUNT(wizyty.idWizyta) AS liczbaWizyt
FROM atrakcje LEFT JOIN wizyty USING(idAtrakcja)
GROUP BY atrakcje.idAtrakcja, atrakcje.nazwa;
USING czy ON

JOIN ... USING(idRegion) działa tylko wtedy, gdy kolumna w obu tabelach nazywa się dokładnie tak samo. Gdy nazwy są różne lub występuje niejednoznaczność (nazwa w obu tabelach), użyj ON i poprzedź kolumnę nazwą tabeli: atrakcje.nazwa.

Zapytania w pliku kwerendy.txt

Zapisz dokładnie te zapytania, które wykonałeś, każde zakończone średnikiem, z numerem w komentarzu (-- 1). Komisja sprawdza treść w pliku lub na zrzucie – musi być uniwersalna (np. WHERE idRodzaj = 10, a nie WHERE nazwa = 'Kamieńczyk').

Sprawdź się — zadanie 1

Czym różni się INNER JOIN od LEFT JOIN w zapytaniu o atrakcje i wizyty?

Pokaż przykładową odpowiedź

JOIN zwróci tylko atrakcje, które mają wizyty, a LEFT JOIN także te bez żadnej wizyty (kolumny z tabeli wizyt będą miały wartość NULL).

Sprawdź się — zadanie 2

Jak nazwać kolumnę z wynikiem COUNT(*) w zapytaniu?

Pokaż przykładową odpowiedź

Przez alias: COUNT(*) AS "Liczba atrakcji" (nazwa z odstępem w apostrofach, cudzysłowie lub odwrotnych apostrofach).

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Wybierz nazwę i cenę atrakcji z Polski, od najdroższej do najtańszej.

SELECT nazwa, cena FROM 
WHERE kraj = 
 cena ;
Ćwiczenie interaktywne — uzupełnij kod

Policz atrakcje z kategorii 2 oraz wylicz średnią cenę w każdym kraju.

SELECT  FROM atrakcje WHERE idKategoria = 2;

SELECT kraj, (cena) FROM atrakcje  kraj;
Ćwiczenie interaktywne — uzupełnij kod

Połącz atrakcje z regionami i wypisz nazwę atrakcji oraz nazwę regionu (z aliasem „region”).

SELECT atrakcje.nazwa, regiony.nazwa  region
FROM atrakcje  regiony  atrakcje.idRegion = regiony.idRegion;
Ćwiczenie interaktywne — popraw kod

Zapytanie ma dwa błędy: operator porównania z innego języka i niepełne sortowanie. Popraw je.

Ćwiczenie interaktywne — uzupełnij kod

Zmień cenę atrakcji o identyfikatorze 3 na 20 zł oraz usuń wizytę o identyfikatorze 5.

 atrakcje  cena = 20 WHERE idAtrakcja = 3;

 wizyty WHERE idWizyta = 5;

Zadanie praktyczne

Baza podróżnicza: import i pięć kwerend

Szacowany czas: 30 minZakres: Lekcje 1–3Forma: zrzuty ekranu i plik kwerendy.txt

Zadanie

Przećwicz rezultat R.1 na bazie ćwiczeniowej, zachowując rygor egzaminacyjny (nazwy plików, pełne zrzuty).

Założenia

  • W phpMyAdmin utwórz bazę podroze (zestaw utf8_unicode_ci), zaimportuj podroze.sql z paczki podroze.zip, wykonaj zrzut import.png (cały ekran).
  • Zapytanie 1: pola nazwa, kraj, cena atrakcji o cenie wyższej niż 20 zł, posortowane malejąco po cenie.
  • Zapytanie 2: nazwy atrakcji z kategorii „Plaża” (idKategoria = 3) – bez użycia nazwy kategorii w warunku.
  • Zapytanie 3: nazwa regionu i liczba atrakcji w regionie (alias „Atrakcji”), grupowanie po regionie.
  • Zapytanie 4: wstaw wizytę: idAtrakcja = 9, idTurysta = 3, data 2026-06-01; klucz podstawowy nadaje baza.
  • Zapytanie 5: nick turysty i nazwa każdej odwiedzonej przez niego atrakcji (relacja m:n).
  • Zapisz zapytania w kwerendy.txt, a zrzuty wyników jako kw1.png…kw5.png.

Ocenie podlegać będzie

  • poprawność importu i zrzutów,
  • poprawność zapytań (warunki, aliasy, relacje),
  • zapis w pliku kwerendy.txt,
  • rygor nazewniczy.
Wskazówki
  • Kwerenda 4 nie ma alias w nazwie tabeli – wartości dat w apostrofach.
  • Relacja m:n wymaga dwóch złączeń.
  • Wzorce: lekcja 3.

Część B

PHP i mysqli w praktyce

Składnia PHP, funkcje mysqli z tabeli arkusza oraz wzorce: listy z bazy, parametry GET, formularze i INSERT, sklep z koszykiem, sesje i LEFT JOIN.

Część B, lekcja 4

PHP w HTML, zmienne i wypisywanie

PHP jest wykonywany po stronie serwera: serwer przetwarza plik .php, wykonuje fragmenty w znacznikach <?php ... ?>, a do przeglądarki wysyła wynik jako zwykły HTML. Dlatego strony z PHP musisz otwierać przez adres http://localhost/... (nie z dysku) i zapisywać z rozszerzeniem .php.

Pierwsza strona z PHP
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Test PHP</title>
</head>
<body>
    <h1>Strona dynamiczna</h1>
    <?php
    $nazwa = "Koloseum";
    $cena = 16;
    echo "<p>Atrakcja: $nazwa</p>";               // cudzysłów: zmienne są podstawiane
    echo '<p>Cena: ' . $cena . ' zł</p>';          // apostrof: tekst dosłowny; kropka skleja napisy
    echo "<p>Za dwie osoby: " . ($cena * 2) . " zł</p>";
    ?>
    <p>Dziś jest <?php echo date("d.m.Y"); ?></p>
</body>
</html>
ZapisWynik
echo "Cześć $imie";w cudzysłowie zmienna jest podstawiana
echo 'Cześć $imie';w apostrofach wypisze się dosłowny tekst $imie
echo "Wartość: {$wiersz['nazwa']}";elementy tablicy w napisie zapisz w klamrach (z apostrofami w kluczu)
echo "Cena: " . $cena . " zł";kropka skleja napisy
<?= $zmienna ?>skrót dla <?php echo $zmienna; ?>
  • Instrukcje kończy średnik; nazwy zmiennych zaczynają się od $ i rozróżniają wielkość liter.
  • Cudzysłów w HTML wewnątrz napisu PHP: użyj apostrofów w atrybutach: echo "<img src='img/{$plik}' alt='zdjęcie'>"; – unikniesz ucieczki znaków.
  • Plik z PHP zapisuj w UTF-8 (bez BOM) i dodaj <meta charset="UTF-8">, inaczej polskie litery się zepsują.

Składnia heredoc: echo <<

Gdy trzeba wypisać większy fragment HTML ze zmiennymi, zapis w cudzysłowach męczy: trzeba pilnować apostrofów i znaków specjalnych. Wygodniejszy jest heredoc – blok tekstu między <<<ETYKIETA a tą samą etykietą w osobnej linii. Wewnątrz bloku piszesz zwykły HTML z cudzysłowami w atrybutach, a zmienne PHP są podstawiane tak samo jak w cudzysłowie.

echo <<
<?php
$nazwa = "Koloseum";
$cena = 16;
$wiersz = ["plik" => "koloseum.svg", "opis" => "Starożytny amfiteatr w Rzymie."];

echo <<<SKRYPT1
<div class="atrakcja">
    <img src="img/{$wiersz['plik']}" alt="$nazwa">
    <h3>$nazwa</h3>
    <p>Cena biletu: $cena zł, dla dwóch osób: {$cena}0 zł</p>
    <p>{$wiersz['opis']}</p>
</div>
SKRYPT1;
?>
ZasadaPrzykład / uwaga
początek: <<< i dowolna etykieta, potem nowa liniaecho <<<SKRYPT1 – nazwa jest dowolna (HTML, KOD, SKRYPT1), wielkość liter ma znaczenie
koniec: ta sama etykieta w osobnej linii i średnikSKRYPT1; – od PHP 7.3 etykieta może być wcięta, ale w starszych wersjach musi stać w pierwszej kolumnie
cudzysłowy w treścinie trzeba ich zapisywać z ukośnikiem – <img src="a.jpg"> wpisujesz normalnie
zmienna prosta$nazwa – podstawiana; gdy tuż po niej stoi litera lub cyfra, otocz ją klamrami: {$nazwa}
element tablicy{$wiersz['nazwa']} (klamry i apostrofy) lub $wiersz[nazwa] (bez apostrofów, bez klamer)
wywołanie funkcjiwprost nie działa; policz wartość wcześniej: $suma = brutto($cena); i wstaw $suma
nowdoc: <<<'ETYKIETA'wersja bez podstawiania zmiennych (jak w apostrofach) – przydatna do dosłownego kodu
Dlaczego heredoc jest wygodny na egzaminie

Blok HTML w skrypcie wygląda jak zwykły fragment strony, więc łatwiej sprawdzić, czy znaczniki są zamknięte i czy atrybuty mają cudzysłowy – a to zmniejsza liczbę błędów Parse error. Podobnie jak echo w cudzysłowach, heredoc spełnia wymaganie „wypisuje element HTML”.

Typowe błędy w heredoc

(1) Po etykiecie kończącej stoi coś więcej niż średnik (spacja przy starszym PHP, komentarz). (2) Etykiety początkowa i końcowa różnią się wielkością liter. (3) Zapomniane klamry przy tablicy z apostrofami: $wiersz['nazwa'] bez klamer jest błędem składni. (4) Brak nowej linii zaraz po <<<ETYKIETA.

Plik .html nie wykona PHP

Zmiana rozszerzenia na .php jest konieczna. Jeśli w przeglądarce widzisz kod <?php ... zamiast wyniku, strona została otwarta z dysku (file://) albo ma złe rozszerzenie.

Sprawdź się — zadanie 1

Dlaczego użytkownik nie widzi kodu PHP w źródle strony?

Pokaż przykładową odpowiedź

Bo serwer wykonuje PHP i wysyła do przeglądarki tylko wynik (HTML).

Sprawdź się — zadanie 2

Czym różni się echo "$x" od echo '$x'?

Pokaż przykładową odpowiedź

W cudzysłowie zostanie wypisana wartość zmiennej, a w apostrofach dosłowny tekst $x.

Sprawdź się — zadanie 3

Jak zakończyć blok heredoc rozpoczęty od echo <<

Pokaż przykładową odpowiedź

Linią zawierającą wyłącznie tę samą etykietę i średnik: SKRYPT1; (wielkość liter musi się zgadzać).

Sprawdź się — zadanie 4

Jak w heredoc wstawić wartość pola wiersza tablicy asocjacyjnej?

Pokaż przykładową odpowiedź

W klamrach z apostrofami: {$wiersz['nazwa']}; zwykła zmienna: $nazwa albo {$nazwa}.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Uzupełnij skrypt, który zapisuje dane w zmiennych i wypisuje zdanie z ich wartościami.


$imie = "Anna";
$wiek = 17;
 "Cześć, $imie! Masz $wiek lat.";
Ćwiczenie interaktywne — popraw kod

W skrypcie brakuje znaków $ przy zmiennej i średnika. Popraw kod tak, aby wypisał „Witaj, Anna”.

Ćwiczenie interaktywne — uzupełnij kod

Wypisz blok HTML ze zmiennymi za pomocą składni heredoc. Uzupełnij otwarcie bloku, element tablicy i zakończenie.

<?php
$nazwa = "Koloseum";
echo 
<h3>$nazwa</h3>
<img src="img/{}" alt="$nazwa">
Ćwiczenie interaktywne — popraw kod

Blok heredoc nie jest poprawnie zamknięty: etykieta końcowa ma inną wielkość liter niż początkowa i brakuje średnika. Popraw kod.

Ćwiczenie interaktywne — uzupełnij kod

Wstaw do zdania wartość pola tablicy $wiersz oraz wypisz tekst bez podstawiania zmiennej (apostrofy).

$wiersz = ["nazwa" => "Rzym"];
echo "Miasto: ";
echo ' w apostrofach jest wypisane dosłownie';

Część B, lekcja 5

Warunki w PHP: if, else, switch i isset

Warunek pozwala wykonać fragment kodu tylko wtedy, gdy coś jest prawdą: użytkownik jest zalogowany, w adresie jest parametr id, formularz został wysłany, koszyk nie jest pusty. W arkuszach INF.03 warunki służą głównie do sprawdzania danych z $_GET, $_POST i $_SESSION.

if, elseif, else
<?php
$ocena = 4;
if ($ocena >= 5) {
    echo "bardzo dobrze";
} elseif ($ocena >= 3) {
    echo "dobrze";
} else {
    echo "do poprawy";
}

// operator trójargumentowy i operator ??
$status = $ocena >= 3 ? "zaliczone" : "niezaliczone";
$id = $_GET["id"] ?? 6;              // wartość domyślna, gdy parametru nie ma

// switch: jedna zmienna, wiele wartości
switch ($status) {
    case "zaliczone":   echo "Gratulacje"; break;
    default:            echo "Spróbuj ponownie";
}
?>
KonstrukcjaUwagi
isset($zmienna)czy zmienna lub element tablicy istnieje i nie jest null (wymienione w arkuszu)
empty($zmienna)prawda dla 0, pustego napisu, null, pustej tablicy
==, ===, !=porównanie; === sprawdza także typ
&&, ||, !i, lub, negacja
$a ?? $bwartość $a, a gdy jej nie ma – $b
Typowe warunki na egzaminie
<?php
session_start();
if (!isset($_SESSION["zalogowany"])) {      // ochrona strony
    header("Location: login.php");
    exit;
}
if (isset($_POST["nazwa"])) {                // formularz został wysłany
    // zapis do bazy
}
if (isset($_GET["id"])) {                    // parametr w adresie
    $id = intval($_GET["id"]);
}
?>
Sprawdź się — zadanie 1

Czym różni się isset($_GET["id"]) od $_GET["id"] ?? 6?

Pokaż przykładową odpowiedź

isset zwraca prawdę lub fałsz, a ?? zwraca samą wartość parametru albo wartość domyślną po prawej stronie.

Sprawdź się — zadanie 2

Dlaczego po header("Location: ...") dopisujemy exit?

Pokaż przykładową odpowiedź

Bo skrypt działa dalej po wysłaniu nagłówka; exit kończy go i zapobiega wypisaniu zawartości chronionej strony.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Sprawdź, czy $a jest większe od $b; jeśli tak, wypisz komunikat.

<?php
$a = 10;
$b = 2;
 ($a  $b) {
    echo "A jest większe od B";
}
Ćwiczenie interaktywne — uzupełnij kod

Uzupełnij drabinkę warunków: powyżej 90 punktów – celujący, od 50 – zaliczone, w przeciwnym razie – niezaliczone.

<?php
$punkty = 72;
if ($punkty >= 90) {
    echo "celujący";
}  ($punkty  50) {
    echo "zaliczone";
}  {
    echo "niezaliczone";
}
Ćwiczenie interaktywne — popraw kod

Strona ma być dostępna tylko po zalogowaniu, a niezalogowany użytkownik ma trafić na stronę logowania. W kodzie warunek działa odwrotnie. Popraw go.

Ćwiczenie interaktywne — uzupełnij kod

Pobierz id z adresu (domyślnie 6) i wypisz, czy jest parzyste, używając operatora trójargumentowego.

<?php
$id = $_GET["id"]  6;
echo ($id % 2 == 0)  "parzyste"  "nieparzyste";
Ćwiczenie interaktywne — popraw kod

Warunek ma sprawdzać, czy $a jest równe $b, ale zamiast porównania stoi przypisanie. Popraw kod.

Część B, lekcja 6

Pętle w PHP: for, while i foreach

Pętla powtarza fragment kodu. Na egzaminie najczęściej pojawia się while z mysqli_fetch_array (wiersze z bazy) oraz foreach po tablicy, np. po koszyku.

Trzy rodzaje pętli
<?php
for ($i = 1; $i <= 3; $i++) {                // znana liczba powtórzeń
    echo "Krok $i<br>";
}

$n = 3;
while ($n > 0) {                             // dopóki warunek jest prawdziwy
    echo "$n ";
    $n--;
}

$miasta = ["Radom", "Kielce", "Lublin"];
foreach ($miasta as $miasto) {               // po wszystkich elementach tablicy
    echo "<li>$miasto</li>";
}

// pętla po wynikach zapytania (wzorzec z lekcji o mysqli)
$wynik = mysqli_query($polaczenie, "SELECT nazwa FROM atrakcje;");
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<li>{$wiersz['nazwa']}</li>";
}
?>
PętlaKiedy
forznana liczba powtórzeń lub potrzebny licznik
whiledopóki warunek jest prawdziwy – np. dopóki zapytanie zwraca kolejny wiersz
foreachprzejście po wszystkich elementach tablicy (także asocjacyjnej)
break, continueprzerwanie pętli, przejście do następnego obrotu
Suma w pętli

Zmienną z sumą ustaw przed pętlą ($suma = 0;), a w pętli dodawaj do niej: $suma += $cena;. Ustawienie zera w środku pętli zeruje wynik w każdym obrocie.

Sprawdź się — zadanie 1

Którą pętlą wypiszesz wszystkie wiersze zwrócone przez zapytanie?

Pokaż przykładową odpowiedź

while ($wiersz = mysqli_fetch_array($wynik)) { ... } – pętla kończy się, gdy funkcja zwróci null.

Sprawdź się — zadanie 2

Jak w pętli sumować wartości?

Pokaż przykładową odpowiedź

Przed pętlą $suma = 0;, w pętli $suma += $cena;.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Wypisz kroki od 1 do 5 pętlą for.

<?php
 ($i = 1; $i  5; $i) {
    echo "Krok $i<br>";
}
Ćwiczenie interaktywne — popraw kod

Pętla while odlicza od 5 do 1, ale nigdy się nie kończy. Popraw kod.

Ćwiczenie interaktywne — uzupełnij kod

Wypisz każde miasto z tablicy jako element listy punktowanej.

<?php
$miasta = ["Radom", "Kielce", "Lublin"];
echo "<ul>";
 ($miasta  $miasto) {
    echo "<li>$miasto</li>";
}
echo "</ul>";
Ćwiczenie interaktywne — popraw kod

Suma cen jest zawsze równa ostatniej cenie, bo zmienna jest zerowana w każdym obrocie pętli. Popraw kod.

Ćwiczenie interaktywne — uzupełnij kod

Wypisz nazwy atrakcji, przechodząc pętlą po wierszach wyniku zapytania.

$wynik = mysqli_query($polaczenie, "SELECT nazwa FROM atrakcje;");
 ($wiersz = ($wynik)) {
    echo "<li>{$wiersz['nazwa']}</li>";
}

Część B, lekcja 7

Tablice w PHP: indeksowane i asocjacyjne

Tablica przechowuje wiele wartości w jednej zmiennej. W PHP wiersz wyniku zapytania jest tablicą, koszyk w sesji też, a dane formularza leżą w tablicach $_GET i $_POST.

Tablica indeksowana i asocjacyjna
<?php
$miasta = ["Radom", "Kielce", "Lublin"];          // indeksy 0, 1, 2
echo $miasta[0];                                    // Radom
$miasta[] = "Płock";                                // dodanie na końcu
echo count($miasta);                                // 4

$atrakcja = ["nazwa" => "Koloseum", "cena" => 16];  // klucze tekstowe
echo $atrakcja["nazwa"] . ", " . $atrakcja["cena"] . " zł";
$atrakcja["cena"] = 18;                             // zmiana wartości

foreach ($atrakcja as $klucz => $wartosc) {
    echo "$klucz: $wartosc<br>";
}

// tablica tablic: np. koszyk w sesji
$koszyk = [["id" => 3, "ilosc" => 2], ["id" => 7, "ilosc" => 1]];
foreach ($koszyk as $pozycja) {
    echo $pozycja["id"] . " x " . $pozycja["ilosc"] . "<br>";
}
?>
FunkcjaOpis
count($tab)liczba elementów
in_array($x, $tab)czy wartość występuje w tablicy
array_key_exists("k", $tab), isset($tab["k"])czy istnieje klucz
unset($tab["k"])usunięcie elementu (np. z koszyka)
implode(", ", $tab)złączenie elementów w napis
Wiersz z bazy to tablica

mysqli_fetch_array zwraca tablicę z kluczami nazw kolumn i numerów, mysqli_fetch_assoc tylko z nazwami, a mysqli_fetch_row tylko z numerami. Dlatego pole odczytujesz jako $wiersz['nazwa'] albo $wiersz[0].

Sprawdź się — zadanie 1

Jak dodać element na koniec tablicy $koszyk?

Pokaż przykładową odpowiedź

$koszyk[] = $element;

Sprawdź się — zadanie 2

Czym różni się tablica indeksowana od asocjacyjnej?

Pokaż przykładową odpowiedź

W indeksowanej elementy mają kolejne numery od 0, w asocjacyjnej – klucze tekstowe, np. $wiersz["nazwa"].

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Utwórz tablicę miast, wypisz pierwszy element, policz elementy i dodaj miasto na końcu.

$miasta = "Radom", "Kielce", "Lublin";
echo $miasta[];                 // Radom
echo ($miasta);             // 3
$miasta = "Płock";
Ćwiczenie interaktywne — uzupełnij kod

Utwórz tablicę asocjacyjną i przejdź po niej, wypisując klucze i wartości.

$atrakcja = ["nazwa"  "Koloseum", "cena" => 16];
echo $atrakcja[];
foreach ($atrakcja as ) {
    echo "$klucz: $wartosc<br>";
}
Ćwiczenie interaktywne — popraw kod

Elementy tablicy asocjacyjnej odczytujemy po kluczu w cudzysłowach. Popraw odczyt nazwy i ceny produktu.

Ćwiczenie interaktywne — popraw kod

Pętla przechodzi po pozycjach koszyka, ale odczytuje dane z całej tablicy zamiast z bieżącej pozycji. Popraw kod.

Ćwiczenie interaktywne — uzupełnij kod

Sprawdź, czy tablica zawiera wartość, a następnie usuń element o kluczu „rabat”.

$kody = ["A1", "B2", "C3"];
if (("B2", $kody)) { echo "jest"; }

$zamowienie = ["suma" => 100, "rabat" => 10];
($zamowienie["rabat"]);

Część B, lekcja 8

Funkcje w PHP: własne i wbudowane

Funkcja to nazwany fragment kodu, który można wywołać wielokrotnie, np. wypisującą jeden blok sklepu lub liczącą cenę brutto. Funkcje pozwalają uniknąć kopiowania tych samych linii.

Własne funkcje
<?php
function brutto($netto, $vat = 23) {                // parametr z wartością domyślną
    return $netto * (1 + $vat / 100);
}
echo number_format(brutto(100), 2, ",", " ");       // 123,00

function blok($wiersz) {                            // funkcja wypisująca HTML
    echo "<div class='produkt'><h3>{$wiersz['nazwa']}</h3><p>{$wiersz['cena']} zł</p></div>";
}

$licznik = 0;
function zwieksz() {
    global $licznik;                                // zmienna spoza funkcji wymaga global
    $licznik++;
}
?>
FunkcjaOpis
number_format($x, 2, ",", " ")formatowanie liczby: dwa miejsca, przecinek dziesiętny
round($x, 2), intval($tekst)zaokrąglenie, zamiana na liczbę całkowitą
date("Y-m-d")bieżąca data
strlen(), strtoupper(), trim()podstawowe operacje na tekście
htmlspecialchars($tekst)zabezpieczenie tekstu przed wstawieniem HTML na stronę
Zasięg zmiennych

Zmienna utworzona poza funkcją jest w funkcji niewidoczna. Przekaż ją jako parametr albo użyj global (np. global $polaczenie; przy funkcji, która wykonuje zapytanie).

Sprawdź się — zadanie 1

Jak w PHP wartość z adresu URL zamienić na liczbę całkowitą?

Pokaż przykładową odpowiedź

intval($_GET["id"]) lub rzutowanie (int)$_GET["id"].

Sprawdź się — zadanie 2

Co wypisze echo "Suma: " . 2 + 3;?

Pokaż przykładową odpowiedź

W PHP 8 operator + ma wyższy priorytet niż ., więc wynik to „Suma: 5”; w razie wątpliwości używaj nawiasów: "Suma: " . (2 + 3).

Sprawdź się — zadanie 3

Po co w funkcji słowo return?

Pokaż przykładową odpowiedź

Zwraca wynik do miejsca wywołania, np. $cena = brutto(100);; funkcja bez return zwraca null.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Napisz funkcję liczącą cenę brutto z domyślną stawką VAT 23%.

 brutto($netto, $vat = 23) {
     $netto * (1 + $vat / 100);
}
echo number_format(brutto(100), 2, ",", " ");
Ćwiczenie interaktywne — uzupełnij kod

Wywołaj funkcję wypisującą blok produktu dla każdego wiersza wyniku.

function blok($wiersz) {
    echo "<div class='produkt'><h3>{$wiersz['nazwa']}</h3></div>";
}
while ($wiersz = mysqli_fetch_array($wynik)) {
    ();
}
Ćwiczenie interaktywne — popraw kod

Funkcja nie widzi zmiennej $polaczenie utworzonej poza nią i zgłasza błąd. Popraw kod (może to być słowo global albo parametr funkcji).

Ćwiczenie interaktywne — popraw kod

Funkcja ma zwrócić kwotę podatku, ale po jej wywołaniu nic nie jest wypisywane. Popraw kod.

Ćwiczenie interaktywne — uzupełnij kod

Zaokrąglij liczbę do dwóch miejsc, zamień napis z adresu na liczbę całkowitą i pobierz bieżącą datę.

echo (3.14159, 2);
$id = ($_GET["id"]);
echo ("Y-m-d");

Część B, lekcja 9

Połączenie z bazą: funkcje mysqli z tabeli arkusza

Arkusz egzaminacyjny zawiera Tabelę 1 z wyborem funkcji MySQLi. Możesz z niej korzystać na egzaminie, ale warto znać ją na pamięć – to szkielet każdego skryptu.

FunkcjaDziałanie / zwracana wartość
mysqli_connect(serwer, użytkownik, hasło, nazwa_bazy)połączenie; zwraca identyfikator połączenia lub false
mysqli_select_db(połączenie, nazwa_bazy)wybór bazy (gdy nie podano jej w connect); true/false
mysqli_query(połączenie, zapytanie)wykonuje zapytanie; dla SELECT zwraca wynik, dla INSERT – true/false
mysqli_fetch_row(wynik)kolejny wiersz jako tablica numeryczna ($w[0], $w[1])
mysqli_fetch_array(wynik)kolejny wiersz jako tablica indeksowana i asocjacyjna ($w[0] oraz $w['nazwa']); null/false, gdy koniec
mysqli_fetch_assoc(wynik)kolejny wiersz jako tablica asocjacyjna
mysqli_num_rows(wynik)liczba wierszy w wyniku
mysqli_num_fields(wynik)liczba kolumn w wyniku
mysqli_error(połączenie)tekst ostatniego błędu
mysqli_close(połączenie)zamknięcie połączenia
Szkielet skryptu z bazą (wzorzec do zapamiętania)
<?php
// 1. połączenie: serwer, użytkownik, hasło, baza (zgodnie z arkuszem)
$polaczenie = mysqli_connect("localhost", "root", "", "podroze");
mysqli_set_charset($polaczenie, "utf8");              // poprawne polskie znaki

// 2. zapytanie
$zapytanie = "SELECT idAtrakcja, nazwa, plik FROM atrakcje WHERE kraj = 'Polska';";
$wynik = mysqli_query($polaczenie, $zapytanie);

// 3. pętla po wierszach wyniku
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<p>{$wiersz['nazwa']}</p>";
}

// 4. zamknięcie połączenia (ostatnia operacja na bazie)
mysqli_close($polaczenie);
?>

Obsługa błędów

Sprawdzenie połączenia i zapytania
$polaczenie = mysqli_connect("localhost", "root", "", "podroze");
if (!$polaczenie) {
    die("Brak połączenia z bazą: " . mysqli_connect_error());     // zatrzymuje skrypt
}
$wynik = mysqli_query($polaczenie, "SELECT * FROM nieistniejaca;");
if (!$wynik) {
    echo "Błąd zapytania: " . mysqli_error($polaczenie);          // np. Table 'podroze.nieistniejaca' doesn't exist
}
echo mysqli_num_rows(mysqli_query($polaczenie, "SELECT * FROM atrakcje")) . " atrakcji";
  • Gdy w mysqli_connect podasz nazwę bazy, mysqli_select_db jest zbędne.
  • mysqli_set_charset($polaczenie, "utf8") nie jest w tabeli arkusza, ale jest funkcją mysqli – dozwolone i zapobiega „krzaczkom” zamiast polskich liter.
  • Na egzaminie hasło do bazy jest puste (""), użytkownik root, serwer localhost.
  • Połączenie otwierasz raz na początku strony i zamykasz na końcu – nie otwieraj nowego przy każdym zapytaniu.
fetch_array czy fetch_assoc

W arkuszach używa się mysqli_fetch_array i odwołuje po nazwach kolumn ($wiersz['nazwa']). mysqli_fetch_assoc daje to samo (bez numerowanych indeksów). Wybierz jedną funkcję i używaj konsekwentnie.

Sprawdź się — zadanie 1

Co zwraca mysqli_fetch_array, gdy skończą się wiersze wyniku?

Pokaż przykładową odpowiedź

null (wartość fałszywa), dlatego sprawdza się ją w warunku pętli while ($wiersz = mysqli_fetch_array($wynik)).

Sprawdź się — zadanie 2

Którą funkcją sprawdzisz liczbę wierszy zwróconych przez zapytanie SELECT?

Pokaż przykładową odpowiedź

mysqli_num_rows($wynik).

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Połącz się z bazą, wykonaj zapytanie i zamknij połączenie.

$polaczenie = ("localhost", "root", "", "podroze");
$wynik = ($polaczenie, "SELECT nazwa FROM atrakcje;");
echo mysqli_num_rows($wynik);
($polaczenie);
Ćwiczenie interaktywne — popraw kod

W skrypcie brakuje argumentów: puste hasło przy łączeniu, połączenie w mysqli_query i w mysqli_close. Popraw wywołania.

Ćwiczenie interaktywne — uzupełnij kod

Sprawdź, czy zapytanie się powiodło; jeśli nie, wypisz komunikat błędu serwera.

$wynik = mysqli_query($polaczenie, $sql);
if () {
    echo "Błąd: " . ($polaczenie);
}
Ćwiczenie interaktywne — popraw kod

Wiersz jest pobierany funkcją zwracającą tablicę numerowaną, więc odczyt po nazwie kolumny nie działa. Popraw wywołanie funkcji pobierającej wiersz.

Część B, lekcja 10

Wzorzec: wypisywanie listy, tabeli, obrazów i opcji z bazy

Większość skryptów z arkuszy to ten sam schemat: zapytanie → pętla while → echo elementu HTML z danymi wiersza. Zmienia się tylko znacznik, który generujesz.

Lista numerowana i punktowana (np. „Wodospady:”)
<?php
$wynik = mysqli_query($polaczenie, "SELECT nazwa FROM atrakcje WHERE kraj = 'Polska' AND idKategoria = 2;");
echo "<ol>";
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<li>{$wiersz['nazwa']}</li>";
}
echo "</ol>";
?>
Wiersze tabeli (nagłówek w HTML, dane ze skryptu)
<table>
    <tr><th>Identyfikator</th><th>Państwo</th><th>Nazwa</th><th>Cena</th></tr>
    <?php
    $wynik = mysqli_query($polaczenie, "SELECT idAtrakcja, kraj, nazwa, cena FROM atrakcje WHERE idRegion = 1;");
    while ($wiersz = mysqli_fetch_array($wynik)) {
        echo "<tr>
                <td>{$wiersz['idAtrakcja']}</td>
                <td>{$wiersz['kraj']}</td>
                <td>{$wiersz['nazwa']}</td>
                <td>{$wiersz['cena']}</td>
              </tr>";
    }
    ?>
</table>
Obrazy-odnośniki z podpisem (miniatury galerii)
<?php
$wynik = mysqli_query($polaczenie, "SELECT idAtrakcja, plik, nazwa FROM atrakcje WHERE kraj = 'Hiszpania';");
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<a href='atrakcja.php?id={$wiersz['idAtrakcja']}'>
            <img class='miniatura' src='img/{$wiersz['plik']}' alt='{$wiersz['nazwa']}' title='{$wiersz['nazwa']}'>
          </a>";
}
?>
Lista rozwijana (kolejne rekordy to opcje: value = id, treść = nick)
<select name="turysta">
    <?php
    $wynik = mysqli_query($polaczenie, "SELECT idTurysta, nick FROM turysci;");
    while ($wiersz = mysqli_fetch_array($wynik)) {
        echo "<option value='{$wiersz['idTurysta']}'>{$wiersz['nick']}</option>";
    }
    ?>
</select>

To samo z heredoc: echo <<
Miniatura galerii i wiersz tabeli bez ucieczki cudzysłowów
<?php
$wynik = mysqli_query($polaczenie, "SELECT idAtrakcja, plik, nazwa, cena FROM atrakcje WHERE kraj = 'Hiszpania';");
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo <<<SKRYPT1
    <a href="atrakcja.php?id={$wiersz['idAtrakcja']}">
        <img class="miniatura" src="img/{$wiersz['plik']}" alt="{$wiersz['nazwa']}" title="{$wiersz['nazwa']}">
    </a>
SKRYPT1;
}

$wynik = mysqli_query($polaczenie, "SELECT idAtrakcja, nazwa, cena FROM atrakcje WHERE idRegion = 1;");
echo "<table><tr><th>Id</th><th>Nazwa</th><th>Cena</th></tr>";
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo <<<SKRYPT2
    <tr>
        <td>{$wiersz['idAtrakcja']}</td>
        <td>{$wiersz['nazwa']}</td>
        <td>{$wiersz['cena']}</td>
    </tr>
SKRYPT2;
}
echo "</table>";
?>
Pętla i heredoc

Blok heredoc może stać wewnątrz pętli – wypisuje się przy każdym obrocie. Etykieta zamykająca może być wcięta (PHP 7.3+; XAMPP z PHP 8.2 na egzaminie to obsługuje), ale pamiętaj o tej samej etykiecie na początku i końcu bloku.

Wymaganie z arkuszaRealizacja
„dla każdego rekordu wyświetla odnośnik”<a href='...'> wewnątrz pętli while
„przekazujący do tej strony metodą GET parametr o wartości równej polu id”href='strona.php?id={$wiersz['idAtrakcja']}'
„odnośnik jest obrazem; tekst alternatywny to pole nazwa; dymek po najechaniu”<img alt='nazwa' title='nazwa'> – dymek tworzy atrybut title
„atrybut wartości to pole id, treść to nick”<option value='id'>nick</option>
„wiersz tabeli wypełniony polami”po jednej komórce td na pole; nagłówek th napisz w HTML
„ścieżka dostępu odpowiednio do kategorii”doklej katalog: src='img/sofy/{$wiersz['plik']}'
Cudzysłowy w echo

Wewnątrz napisu w cudzysłowach PHP używaj apostrofów dla atrybutów HTML. Zapis echo "<img src="img/{$p}">" kończy napis w środku i powoduje Parse error. Elementy tablicy w napisie z cudzysłowami: {$wiersz['nazwa']} (klamry!), bez klamer $wiersz['nazwa'] działa tylko bez apostrofów.

Sprawdź się — zadanie 1

Jak sprawić, aby obraz-odnośnik pokazywał dymek z nazwą po najechaniu kursorem?

Pokaż przykładową odpowiedź

Dodać do obrazu atrybut title o wartości pola nazwa (oraz alt jako tekst alternatywny).

Sprawdź się — zadanie 2

Jak wygenerować listę rozwijaną z identyfikatorami jako wartościami i nazwami jako tekstem opcji?

Pokaż przykładową odpowiedź

Pętlą po wynikach zapytania wypisać <option value='id'>nazwa</option> wewnątrz znacznika <select>.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Wypisz listę rozwijaną regionów: wartością opcji jest identyfikator, a tekstem nazwa.

echo "<select name='region'>";
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<option value='{$wiersz['idRegion']}'>{$wiersz['']}</option>";
}
echo "";
Ćwiczenie interaktywne — uzupełnij kod

Wypisz tabelę z nazwą i ceną atrakcji. Uzupełnij znaczniki wiersza i zamknięcie tabeli.

echo "<table>";
echo "<tr><th>Nazwa</th><th>Cena</th></tr>";
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<><td>{$wiersz['nazwa']}</td><td>{$wiersz['cena']} zł</td></>";
}
echo "</>";
Ćwiczenie interaktywne — popraw kod

Pętla wypisuje w nieskończoność pierwszy wiersz, bo wiersz jest pobierany tylko raz przed pętlą. Popraw pętlę.

Ćwiczenie interaktywne — popraw kod

Wypisanie obrazu powoduje błąd składni, bo elementy tablicy w napisie z apostrofami są zapisane bez klamer. Popraw instrukcję echo.

Ćwiczenie interaktywne — uzupełnij kod

Wypisz miniaturę-odnośnik za pomocą heredoc w pętli.

while ($wiersz = mysqli_fetch_array($wynik)) {
    echo 
<a href="atrakcja.php?id={$wiersz['idAtrakcja']}">{$wiersz['nazwa']}</a>

}

Część B, lekcja 11

Parametry GET: odnośniki, wartość domyślna, zapytanie z parametrem

Dane przekazane w adresie URL (strona.php?id=5&kat=2) odczytujesz w tablicy $_GET. Dzięki temu jedna strona może pokazać różne dane w zależności od klikniętego odnośnika.

Wzorzec 1: strona szczegółów (atrakcja.php) – parametr wymagany
<?php
$polaczenie = mysqli_connect("localhost", "root", "", "podroze");
mysqli_set_charset($polaczenie, "utf8");

if (isset($_GET["id"])) {                                   // czy parametr został przesłany
    $id = intval($_GET["id"]);                              // zamiana na liczbę całkowitą
    $zapytanie = "SELECT plik, atrakcje.nazwa, kategorie.nazwa AS kategoria, cena, ocena, opis
                  FROM atrakcje JOIN kategorie USING(idKategoria)
                  WHERE idAtrakcja = $id;";
    $wynik = mysqli_query($polaczenie, $zapytanie);
    $wiersz = mysqli_fetch_array($wynik);                   // jeden wiersz – bez pętli

    echo "<img src='img/{$wiersz['plik']}' alt='{$wiersz['nazwa']}'>";
    echo "<h2>{$wiersz['nazwa']}</h2>";
    echo "<h3>{$wiersz['kategoria']}</h3>";
    echo "<p>Cena: {$wiersz['cena']} zł, ocena: {$wiersz['ocena']}</p>";
    echo "<p>{$wiersz['opis']}</p>";
}
mysqli_close($polaczenie);
?>
Wzorzec 2: odnośniki do tej samej strony i wartość domyślna
<?php
// skrypt 1: menu regionów – odnośniki prowadzą do tej samej strony i przesyłają identyfikator
$wynik = mysqli_query($polaczenie, "SELECT idRegion, nazwa FROM regiony;");
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<a href='index.php?idRegion={$wiersz['idRegion']}'>{$wiersz['nazwa']}</a>";
}

// skrypt 2: tabela dla wybranego regionu; gdy brak parametru – domyślnie region 1
$region = isset($_GET["idRegion"]) ? intval($_GET["idRegion"]) : 1;     // lub: intval($_GET["idRegion"] ?? 1)
$wynik = mysqli_query($polaczenie, "SELECT idAtrakcja, kraj, nazwa, cena FROM atrakcje WHERE idRegion = $region;");
echo "<table><tr><th>Id</th><th>Kraj</th><th>Nazwa</th><th>Cena</th></tr>";
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<tr><td>{$wiersz['idAtrakcja']}</td><td>{$wiersz['kraj']}</td><td>{$wiersz['nazwa']}</td><td>{$wiersz['cena']}</td></tr>";
}
echo "</table>";
?>
Wymaganie z arkuszaRealizacja
„pobiera wartość przesłaną metodą GET, a w przypadku braku – wartość 6”isset($_GET['id']) ? $_GET['id'] : 6 lub $_GET['id'] ?? 6
„zapytanie zmodyfikowane tak, że sprawdzany jest identyfikator z adresu”wstaw zmienną do WHERE: WHERE idRodzaj = 10 AND idKontynent = $id
„skrypt działa tylko, gdy przesłano parametr”otocz kod warunkiem if (isset($_GET['id'])) { ... }
„przekazuje do tej strony parametr”href='obiekty.php?idObiekt={$wiersz['idObiekt']}'
Zaufane dane na egzaminie, ostrożność w praktyce

Arkusz zakłada, że użytkownik wpisuje tylko poprawne identyfikatory, więc intval() wystarcza. W prawdziwej aplikacji parametr z URL zawsze traktuj jako niezaufany (patrz lekcja 13): rzutuj na liczbę lub użyj zapytań przygotowanych. Wstawienie surowego $_GET do SQL to podatność SQL Injection.

Sprawdź się — zadanie 1

Jak odczytać parametr id z adresu index.php?id=7?

Pokaż przykładową odpowiedź

$_GET["id"] – najlepiej z isset lub ?? i rzutowaniem na liczbę: intval($_GET["id"] ?? 1).

Sprawdź się — zadanie 2

Jak zapisać odnośnik, który przekaże identyfikator rekordu do tej samej strony?

Pokaż przykładową odpowiedź

echo "<a href='index.php?idRegion={$wiersz['idRegion']}'>{$wiersz['nazwa']}</a>";

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Zbuduj odnośnik, który przekaże identyfikator atrakcji do strony atrakcja.php w parametrze id.

echo "<a href='atrakcja.php?={$wiersz['idAtrakcja']}'>{$wiersz['nazwa']}</a>";
Ćwiczenie interaktywne — uzupełnij kod

Odczytaj parametr z adresu jako liczbę (domyślnie 1) i wstaw go do zapytania.

$id = ($_GET["id"]  1);
$wynik = mysqli_query($polaczenie, "SELECT nazwa, opis FROM atrakcje WHERE idAtrakcja = ;");
Ćwiczenie interaktywne — popraw kod

Wartość jest przekazywana w adresie strony (metoda GET), ale skrypt odczytuje ją z tablicy metody POST. Popraw kod.

Ćwiczenie interaktywne — popraw kod

Wartość tekstowa w zapytaniu SQL musi być w apostrofach. Popraw zapytanie, które ma wybrać atrakcje z podanego kraju.

Ćwiczenie interaktywne — uzupełnij kod

Pokaż szczegóły tylko wtedy, gdy parametr id został przekazany; w przeciwnym razie wypisz komunikat.

if (($_GET["id"])) {
    echo "Szczegóły atrakcji";
}  {
    echo "Wybierz atrakcję z galerii.";
}

Część B, lekcja 12

Formularze POST i zapis do bazy (INSERT)

Formularz i jego obsługa w tej samej stronie
<h4>Zapisz wizytę</h4>
<form action="index.php" method="post">
    <label for="atrakcja">Identyfikator atrakcji</label>
    <input type="number" name="atrakcja" id="atrakcja">

    <label for="turysta">Turysta</label>
    <select name="turysta" id="turysta">
        <?php
        $wynik = mysqli_query($polaczenie, "SELECT idTurysta, nick FROM turysci;");
        while ($wiersz = mysqli_fetch_array($wynik)) {
            echo "<option value='{$wiersz['idTurysta']}'>{$wiersz['nick']}</option>";
        }
        ?>
    </select>
    <button type="submit" name="wyslij">Wpisz</button>
</form>

<?php
if (isset($_POST["wyslij"])) {                          // skrypt działa tylko po wysłaniu formularza
    $atrakcja = intval($_POST["atrakcja"]);
    $turysta = intval($_POST["turysta"]);
    $zapytanie = "INSERT INTO wizyty (idAtrakcja, idTurysta, dataWizyty) VALUES ($atrakcja, $turysta, CURDATE());";
    mysqli_query($polaczenie, $zapytanie);
}
?>
ElementZnaczenie
method="post"metoda „bezpieczna” z arkusza: dane trafiają do $_POST, nie widać ich w adresie
action="index.php"ten sam plik; atrybut można pominąć (formularz wysyła się do bieżącej strony)
name polato klucz w $_POST['name'] – bez name pole nie zostanie wysłane
<button name="wyslij">do $_POST trafia też przycisk, więc isset($_POST['wyslij']) wykrywa wysłanie formularza
value przyciskuprzekazuje wartość (np. id produktu przy przycisku „KUP”)
CURDATE(), NOW()bieżąca data / data i godzina w SQL

Przekierowanie po zapisie

Wzorzec POST → Redirect → GET
<?php
// to musi być na początku pliku, przed jakimkolwiek wyjściem HTML!
if (isset($_POST["wyslij"])) {
    $polaczenie = mysqli_connect("localhost", "root", "", "podroze");
    mysqli_query($polaczenie, "INSERT INTO wizyty (idAtrakcja, idTurysta, dataWizyty) VALUES (5, 2, CURDATE());");
    mysqli_close($polaczenie);
    header("Location: index.php");                      // przekierowanie (Tabela 3 arkusza)
    exit();                                             // zatrzymaj skrypt
}
?>
headers already sent

Funkcja header() działa tylko przed wysłaniem jakiegokolwiek wyjścia, nawet spacji lub pustej linii przed <?php. Objaw: ostrzeżenie „Cannot modify header information – headers already sent”. Przekierowanie umieść na samym początku pliku.

Sprawdź się — zadanie 1

Po co po zapisie do bazy robi się przekierowanie header("Location: ...")?

Pokaż przykładową odpowiedź

Aby odświeżenie strony (F5) nie wysłało formularza ponownie i nie zdublowało rekordu.

Sprawdź się — zadanie 2

Jak sprawdzić, że formularz został wysłany?

Pokaż przykładową odpowiedź

if (isset($_POST["nazwa_przycisku"])) { ... } – przycisk z atrybutem name trafia do $_POST.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Uzupełnij formularz HTML wysyłający pole nick metodą POST do skryptu dodaj.php.

<form ="dodaj.php" ="">
    <input type="text" ="nick">
    <input type="submit" value="Zapisz">
</form>
Ćwiczenie interaktywne — uzupełnij kod

Zapisz nowego turystę do bazy, gdy formularz został wysłany.

if (isset($_POST["nick"])) {
    $nick = $_POST["nick"];
    $sql = " turysci (nick, miasto)  ('$nick', 'Radom');";
    mysqli_query($polaczenie, $sql);
}
Ćwiczenie interaktywne — popraw kod

Zapytanie INSERT zawiera datę wstawioną bez apostrofów, więc zostanie zinterpretowana jako działanie matematyczne. Popraw zapytanie.

Ćwiczenie interaktywne — popraw kod

Formularz wysyła dane metodą POST, a skrypt czyta je z tablicy GET, ponadto argumenty mysqli_query są w złej kolejności. Popraw kod.

Ćwiczenie interaktywne — uzupełnij kod

Po zapisaniu danych przekieruj użytkownika na stronę główną i zakończ skrypt.

mysqli_query($polaczenie, $sql);
header("");
;

Część B, lekcja 13

Wzorzec: sklep z koszykiem i funkcją generującą bloki

Sytuacja z arkusza (05): na stronie są trzy listy produktów (sofy, fotele, komody). Każdy produkt to blok z obrazem, informacjami i przyciskiem „KUP”; przycisk wysyła formularz z identyfikatorem produktu. Obok jest koszyk: lista kupionych produktów i suma. Trzy warianty skryptu różnią się wyłącznie kategorią i katalogiem obrazów. Do ćwiczeń: baza sklep z paczki sklep.zip.

Funkcja zamiast kopiowania trzech skryptów
<?php
$polaczenie = mysqli_connect("localhost", "root", "", "sklep");
mysqli_set_charset($polaczenie, "utf8");

// obsługa przycisku KUP: kupuje zawsze klient 1, jedna sztuka
if (isset($_POST["kup"])) {
    $idProduktu = intval($_POST["kup"]);
    mysqli_query($polaczenie, "INSERT INTO zakupy (idKlient, idProdukt, sztuki) VALUES (1, $idProduktu, 1);");
    header("Location: index.php");
    exit();
}

function wypiszProdukty($polaczenie, $kategoria, $katalog) {          // skrypt 1: wariant a, b, c
    $wynik = mysqli_query($polaczenie, "SELECT idProdukt, nazwa, plik, marka, cena, opis FROM produkty WHERE idKategoria = $kategoria;");
    while ($wiersz = mysqli_fetch_array($wynik)) {
        echo "<div class='produkt'>
                <div class='obraz'><img src='img/$katalog/{$wiersz['plik']}' alt='produkt'></div>
                <div class='informacje'>
                    <h3>{$wiersz['nazwa']}</h3>
                    <h4>marka {$wiersz['marka']}</h4>
                    <h3>CENA: {$wiersz['cena']} zł</h3>
                    <form action='index.php' method='post'>
                        <button type='submit' name='kup' value='{$wiersz['idProdukt']}'>KUP</button>
                    </form>
                </div>
                <div class='opis'>{$wiersz['opis']}</div>
              </div>";
    }
}
?>
<h2>- Gitary -</h2>   <?php wypiszProdukty($polaczenie, 1, "gitary"); ?>
<h2>- Klawisze -</h2> <?php wypiszProdukty($polaczenie, 2, "klawisze"); ?>
<h2>- Perkusje -</h2> <?php wypiszProdukty($polaczenie, 3, "perkusje"); ?>

<aside>
    <h2>Koszyk</h2>
    <p>Zalogowano: Anna Kowalska</p>
    <?php
    $wynik = mysqli_query($polaczenie, "SELECT nazwa, cena FROM produkty JOIN zakupy USING(idProdukt) WHERE idKlient = 1;");
    $koszt = 0;
    echo "<ol>";
    while ($wiersz = mysqli_fetch_array($wynik)) {
        echo "<li>{$wiersz['nazwa']}, cena: {$wiersz['cena']}</li>";
        $koszt += $wiersz['cena'];                                       // suma w pętli
    }
    echo "</ol>";
    echo "<p>Koszt całkowity: " . number_format($koszt, 2, ",", " ") . " zł</p>";   // przy pustym koszyku: 0,00
    mysqli_close($polaczenie);
    ?>
</aside>
  • Funkcja z parametrami ($kategoria, $katalog) zastępuje trzykrotne kopiowanie skryptu – krócej, mniej błędów i lepsza ocena za jakość kodu. Arkusz dopuszcza to (wymaga „wariantów skryptu”, nie ich osobnych plików).
  • Przycisk name='kup' value='{id}' przekazuje identyfikator w $_POST['kup'].
  • Koszyk liczysz w tym samym skrypcie: $koszt += $cena. Gdy lista jest pusta, suma wynosi 0 – nie dziel przez liczbę elementów.
  • Funkcje (function) deklaruj przed użyciem lub w dowolnym miejscu pliku na najwyższym poziomie (PHP wczytuje je z góry).
Nazwy plików obrazów

Plik z bazy (aria.svg) łączysz ze ścieżką dostępu zależną od kategorii: img/gitary/aria.svg. W arkuszu: „ścieżka dostępu odpowiednio do kategorii: sofy/, fotele/, komody/”.

Sprawdź się — zadanie 1

Dlaczego warto trzy warianty skryptu zapisać jako funkcję z parametrami?

Pokaż przykładową odpowiedź

Bo różnią się tylko kategorią i katalogiem obrazów; jedna funkcja wywołana trzy razy ogranicza powtórzenia i ryzyko błędów.

Sprawdź się — zadanie 2

Jak policzyć koszt całkowity koszyka podczas wypisywania listy?

Pokaż przykładową odpowiedź

Zainicjować zmienną $koszt = 0; i w pętli dodawać cenę: $koszt += $wiersz['cena'];.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Funkcja wypisuje blok produktu; uzupełnij formatowanie ceny do dwóch miejsc po przecinku.

function produkt($wiersz) {
    echo "<div class='produkt'>";
    echo "<h3>{$wiersz['nazwa']}</h3>";
    echo "<p>Cena: " . ($wiersz['cena'], 2, ",", " ") . " zł</p>";
    echo "</div>";
}
Ćwiczenie interaktywne — uzupełnij kod

Utwórz w sesji pusty koszyk, jeśli jeszcze nie istnieje, i dodaj do niego identyfikator produktu z adresu.

session_start();
if (!isset($_SESSION["koszyk"])) {
    $_SESSION["koszyk"] = ;
}
$_SESSION["koszyk"] = $_GET["id"];
Ćwiczenie interaktywne — popraw kod

Wartość do zapłaty jest zawsze równa cenie ostatniego produktu, bo kolejne ceny nie są dodawane. Popraw kod.

Ćwiczenie interaktywne — popraw kod

Dodanie do koszyka nie działa, bo skrypt nie uruchomił sesji. Popraw kod.

Ćwiczenie interaktywne — uzupełnij kod

Wybierz produkty z podanej marki, posortowane rosnąco według ceny.

$marka = $_GET["marka"];
$sql = "SELECT nazwa, cena FROM produkty WHERE marka = '$marka'  cena ;";

Część B, lekcja 14

Sesje: logowanie, ochrona strony i wylogowanie

Protokół HTTP nie pamięta użytkownika między żądaniami. Sesja przechowuje dane (np. nazwę zalogowanego użytkownika) na serwerze, a w przeglądarce zostaje tylko identyfikator sesji w ciasteczku. Arkusz zawiera Tabelę 2 z funkcjami sesji i Tabelę 3 z funkcją header.

FunkcjaOpis (z arkusza)
session_start()rozpoczyna sesję i umożliwia dostęp do zmiennych sesyjnych; przed jakimkolwiek wyjściem
$_SESSION['nazwa'] = wartośćustawia lub zmienia zmienną sesyjną
unset($_SESSION['nazwa'])usuwa zmienną sesyjną
session_destroy()usuwa wszystkie zmienne sesyjne i kończy sesję
header("Location: [URL]");przekierowuje klienta do innego adresu
login.php – formularz i sprawdzenie danych
<?php
session_start();
if (isset($_POST["zaloguj"])) {
    if ($_POST["login"] === "admin" && $_POST["haslo"] === "TajneHaslo321*") {
        $_SESSION["uzytkownik"] = "admin";              // zapamiętanie zalogowanego
        header("Location: wyniki.php");
        exit();
    }
    $komunikat = "Błędny login lub hasło";
}
?>
<!DOCTYPE html>
<html lang="pl">
<head><meta charset="UTF-8"><title>Zaloguj się</title><link rel="stylesheet" href="styl.css"></head>
<body>
    <header><h1>Portal biblioteki</h1></header>
    <main id="logowanie">
        <h2>Zaloguj się do systemu</h2>
        <form action="login.php" method="post">
            <label for="login">Login:</label>
            <input type="text" name="login" id="login"><br>
            <label for="haslo">Hasło:</label>
            <input type="password" name="haslo" id="haslo"><br>
            <button type="submit" name="zaloguj">Zaloguj</button>
        </form>
        <?php if (isset($komunikat)) echo "<p>$komunikat</p>"; ?>
    </main>
    <footer><p>Wykonał: 12345</p></footer>
</body>
</html>
wyniki.php – ochrona strony i powitanie
<?php
session_start();
if (!isset($_SESSION["uzytkownik"])) {                   // brak zmiennej sesyjnej = niezalogowany
    header("Location: login.php");
    exit();
}
?>
<!DOCTYPE html>
<html lang="pl">
<head><meta charset="UTF-8"><title>Przeglądaj wyniki</title><link rel="stylesheet" href="styl.css"></head>
<body>
    <header>
        <h1>Portal biblioteki</h1>
        <p>Witaj, <strong><?php echo $_SESSION["uzytkownik"]; ?></strong></p>
        <p><a href="wyloguj.php">Wyloguj</a></p>
    </header>
    <!-- ... tabela z wynikami (lekcja 15) ... -->
</body>
</html>
wyloguj.php
<?php
session_start();
if (isset($_SESSION["uzytkownik"])) {
    unset($_SESSION["uzytkownik"]);                      // usunięcie zmiennej
    session_destroy();                                    // koniec sesji
}
header("Location: login.php");
exit();
Znacznik strong

Arkusz często wymaga zapisania nazwy użytkownika w „znaczniku semantycznym oznaczającym tekst ważny, domyślnie pogrubionym” – to <strong> (a <em> to tekst uwypuklony, domyślnie pochylony).

Sesja a bezpieczeństwo

Egzamin sprawdza hasło porównaniem w kodzie (TajneHaslo321*). W prawdziwej aplikacji haseł nie trzyma się w kodzie ani jawnie w bazie: używa się password_hash() i password_verify(), po zalogowaniu session_regenerate_id(true), a ciasteczko sesyjne oznacza HttpOnly/Secure.

Sprawdź się — zadanie 1

Co trzeba wywołać na początku skryptu, aby używać $_SESSION?

Pokaż przykładową odpowiedź

session_start(); – przed jakimkolwiek wyjściem do przeglądarki (przed HTML).

Sprawdź się — zadanie 2

Jak zabezpieczyć stronę wyniki.php przed wejściem bez logowania?

Pokaż przykładową odpowiedź

Na początku pliku sprawdzić isset($_SESSION["uzytkownik"]); gdy go brak, przekierować header("Location: login.php"); exit();.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Sprawdź dane logowania w bazie; jeśli istnieje pasujący użytkownik, zapisz go w sesji i przekieruj na panel.

session_start();
$wynik = mysqli_query($polaczenie, "SELECT idUzytkownik FROM uzytkownicy WHERE login = '$login' AND haslo = '$haslo';");
if (($wynik)  0) {
    $_SESSION["uzytkownik"] = $login;
    header("Location: panel.php");
    exit;
}
Ćwiczenie interaktywne — uzupełnij kod

Wyloguj użytkownika: uruchom sesję, zniszcz ją i wróć na stronę główną.

();
();
header("Location: index.php");
exit;
Ćwiczenie interaktywne — popraw kod

Strona chroniona nie działa, bo brakuje uruchomienia sesji i zatrzymania skryptu po przekierowaniu. Popraw kod.

Ćwiczenie interaktywne — popraw kod

Powitanie nie wypisuje nazwy użytkownika, bo nazwa superglobalnej tablicy sesji jest zapisana niepoprawnie. Popraw kod.

Część B, lekcja 15

Dane opcjonalne: LEFT JOIN, odnośniki do plików i daty

Sytuacja z arkusza (10): tabela wyników pokazuje wszystkie zlecenia, także te, którym nie przypisano jeszcze wyniku. Gdy plik wyniku istnieje, w ostatniej kolumnie ma być odnośnik otwierający go w nowej karcie. Odpowiednikiem w bazie ćwiczeniowej biblioteka (biblioteka.zip) są wypożyczenia i opcjonalne potwierdzenia zwrotu.

Zapytanie z LEFT JOIN
-- wszystkie wypożyczenia, także te bez zwrotu (dataZwrotu i potwierdzenie = NULL)
SELECT czytelnicy.imie, czytelnicy.nazwisko, ksiazki.tytul,
       wypozyczenia.dataWypozyczenia, zwroty.dataZwrotu, zwroty.potwierdzenie
FROM wypozyczenia
JOIN czytelnicy ON wypozyczenia.idCzytelnik = czytelnicy.idCzytelnik
JOIN ksiazki    ON wypozyczenia.idKsiazka   = ksiazki.idKsiazka
LEFT JOIN zwroty ON zwroty.idWypozyczenie   = wypozyczenia.idWypozyczenie
ORDER BY wypozyczenia.dataWypozyczenia;
Tabela z warunkowym odnośnikiem
<?php
$wynik = mysqli_query($polaczenie, $zapytanie);          // zapytanie powyżej
echo "<table><tr><th>Czytelnik</th><th>Tytuł</th><th>Data wypożyczenia</th><th>Data zwrotu</th><th>Potwierdzenie</th></tr>";
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<tr>
            <td>{$wiersz['imie']} {$wiersz['nazwisko']}</td>
            <td>{$wiersz['tytul']}</td>
            <td>{$wiersz['dataWypozyczenia']}</td>
            <td>" . ($wiersz['dataZwrotu'] ?? "nie zwrócono") . "</td><td>";
    if (!empty($wiersz['potwierdzenie'])) {              // gdy zwrócono niepustą nazwę pliku
        echo "<a href='{$wiersz['potwierdzenie']}' target='_blank'>potwierdzenie</a>";
    }
    echo "</td></tr>";
}
echo "</table>";
?>
PotrzebaRozwiązanie
wiersze bez odpowiednika w drugiej tabeliLEFT JOIN (wszystkie wiersze lewej tabeli)
pusta wartość z bazyNULL w PHP to null; sprawdzaj !empty($pole) lub $pole !== null
odnośnik w nowej karcietarget='_blank'
data dla każdego dnia w grupieSELECT DATE(dataWypozyczenia), COUNT(*) FROM wypozyczenia GROUP BY DATE(dataWypozyczenia)
data bieżąca w SQL i PHPCURDATE() / date("Y-m-d")
Funkcje daty z arkusza

Arkusz zawiera tabelę z funkcjami SQL: CURDATE(), CURTIME(), YEAR(), MONTH(), DAY(), HOUR(), MINUTE(), SECOND() i DATE(). Przykład: wypożyczenia z 2026 roku: WHERE YEAR(dataWypozyczenia) = 2026.

Sprawdź się — zadanie 1

Którego złączenia użyjesz, aby wypisać wszystkie zlecenia niezależnie od tego, czy mają wynik?

Pokaż przykładową odpowiedź

LEFT JOIN tabeli wyników do tabeli zleceń.

Sprawdź się — zadanie 2

Jak w PHP wypisać odnośnik tylko wtedy, gdy pole z nazwą pliku nie jest puste?

Pokaż przykładową odpowiedź

Sprawdzić if (!empty($wiersz['plik'])) { echo "<a href='...'>...</a>"; }.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Wypisz wszystkich turystów wraz z datami wizyt, także tych, którzy jeszcze nigdzie nie byli.

SELECT turysci.nick, wizyty.dataWizyty
FROM turysci
 wizyty  turysci.idTurysta = wizyty.idTurysta;
Ćwiczenie interaktywne — uzupełnij kod

Gdy turysta nie ma wizyty, pole z datą jest puste (null). Wypisz wtedy tekst „brak wizyt”.

while ($wiersz = mysqli_fetch_array($wynik)) {
    $data = $wiersz["dataWizyty"]  "brak wizyt";
    echo "<li>{$wiersz['nick']}: $data</li>";
}
Ćwiczenie interaktywne — popraw kod

Zapytanie ma wypisać liczbę wizyt każdego turysty, także tego z zerem wizyt, ale złączenie pomija turystów bez wizyt. Popraw złączenie.

Ćwiczenie interaktywne — uzupełnij kod

Dla każdej atrakcji policz wizyty i posortuj od najpopularniejszej.

SELECT atrakcje.nazwa, (wizyty.idWizyta) AS liczba
FROM atrakcje LEFT JOIN wizyty ON atrakcje.idAtrakcja = wizyty.idAtrakcja
 atrakcje.nazwa
ORDER BY liczba ;

Zadanie praktyczne

Strona z listami, parametrem GET i formularzem

Szacowany czas: 60 minZakres: Lekcje 4–9Forma: pliki index.php, styl.css i kwerendy.txt

Zadanie

Na bazie podroze (podroze.zip) wykonaj witrynę „Atlas atrakcji” zgodnie z wymaganiami.

Założenia

  • Struktura: header (nagłówek h1 „Atlas atrakcji”), main z aside i section, article, footer z numerem zdającego; HTML5, lang="pl", UTF-8, zewnętrzny styl.css (układ Grid lub Flex).
  • Skrypt 1 (w aside): odnośniki z nazwami regionów; każdy prowadzi do index.php z parametrem GET region.
  • Skrypt 2 (w section): tabela o czterech kolumnach (Id, Kraj, Nazwa, Cena) z atrakcjami wybranego regionu; gdy brak parametru, domyślnie region 1.
  • Skrypt 3: lista rozwijana z turystami (value = id, tekst = nick) i pole liczbowe „identyfikator atrakcji”; przycisk „Zapisz wizytę”.
  • Skrypt 4: po wysłaniu formularza wstawia do tabeli wizyty rekord z datą bieżącą (CURDATE()), a następnie przekierowuje do index.php.
  • W article: trzy obrazy atrakcji z kategorii „Park narodowy” (skrypt z zapytaniem, alt = nazwa).
  • Połączenie z bazą na początku i jego zamknięcie na końcu strony; zapytania zapisz też w kwerendy.txt.

Ocenie podlegać będzie

  • poprawna struktura i CSS,
  • skrypty 1–4 zgodne z wymaganiami,
  • parametr GET i wartość domyślna,
  • przekierowanie po zapisie,
  • połączenie i zamknięcie bazy.
Wskazówki
  • Skrypt 2: $region = intval($_GET['region'] ?? 1).
  • Skrypt 4 umieść na początku pliku przed HTML.
  • Wzorce: lekcje 7–9.

Zadanie praktyczne

Mini-sklep z koszykiem

Szacowany czas: 75 minZakres: Lekcje 4–10Forma: pliki index.php, styl.css, kwerendy.txt

Zadanie

Na bazie sklep (sklep.zip) wykonaj witrynę sklepu muzycznego z koszykiem klienta o identyfikatorze 2.

Założenia

  • Trzy bloki produktów (gitary, klawisze, perkusje) wygenerowane jedną funkcją z parametrami; każdy produkt: obraz (ścieżka zależna od kategorii), nazwa, marka, cena i przycisk „KUP”.
  • Przycisk „KUP” (metoda POST do tego samego pliku) dodaje produkt do tabeli zakupy dla klienta o identyfikatorze 2, jedna sztuka, po czym następuje przekierowanie.
  • W bloku bocznym lista numerowana kupionych produktów klienta 2 w postaci „<nazwa>, cena: <cena>”, pod nią „Koszt całkowity: <suma>”; pusty koszyk daje 0.
  • Formularz filtrowania: lista rozwijana „Marka” (marki z bazy, bez powtórzeń przez DISTINCT) – po wyborze w sekcji pojawiają się tylko produkty tej marki.
  • Zapytania do kwerendy.txt: (1) produkty kategorii 1, (2) wstawienie zakupu, (3) nazwa i cena produktów klienta 2 (relacja), (4) liczba produktów w kategoriach z aliasem.

Ocenie podlegać będzie

  • generowanie bloków i funkcja,
  • przycisk KUP i INSERT,
  • koszyk i suma,
  • filtrowanie po marce,
  • rygor nazewniczy i kwerendy.
Wskazówki
  • Marki: SELECT DISTINCT marka FROM produkty ORDER BY marka.
  • Filtrowanie: wartość z listy w $_GET albo $_POST – ustal i trzymaj jedną metodę.
  • Wzorzec: lekcja 10.

Część C

Bezpieczeństwo, debugowanie i pełne rozwiązania

SQL Injection, XSS i hasła, diagnostyka błędów, trzy kompletne zadania wzorcowe oraz lista kontrolna i strategia egzaminu.

Część C, lekcja 16

Bezpieczeństwo: SQL Injection, XSS i hasła

Arkusz egzaminacyjny pozwala budować zapytania przez wstawienie zmiennej do napisu SQL (zakłada, że użytkownik wpisuje poprawne identyfikatory). W prawdziwej aplikacji to poważna luka. Znajomość zagrożeń i zabezpieczeń jest częścią kwalifikacji INF.03 (bezpieczeństwo aplikacji internetowych), a zapytania przygotowane są w pełni dopuszczalne także na egzaminie – korzystają z biblioteki MySQLi.

SQL Injection – jak działa

Podatny skrypt logowania
<?php
// NIEBEZPIECZNE: dane od użytkownika wklejone do SQL
$login = $_POST["login"];
$haslo = $_POST["haslo"];
$zapytanie = "SELECT * FROM uzytkownicy WHERE login = '$login' AND haslo = '$haslo';";

// atakujący wpisuje w pole loginu:   ' OR '1'='1' --
// powstaje:  SELECT * FROM uzytkownicy WHERE login = '' OR '1'='1' -- ' AND haslo = '';
// warunek jest zawsze prawdziwy, a reszta zapytania to komentarz → logowanie bez hasła
Zapytania przygotowane w stylu proceduralnym mysqli
<?php
$polaczenie = mysqli_connect("localhost", "root", "", "podroze");
mysqli_set_charset($polaczenie, "utf8");

$kraj = $_GET["kraj"] ?? "Polska";
$cenaMax = (float)($_GET["cena"] ?? 100);

// znaki zapytania zastępują wartości; typy: s – napis, i – liczba całkowita, d – liczba rzeczywista
$stmt = mysqli_prepare($polaczenie, "SELECT idAtrakcja, nazwa, cena FROM atrakcje WHERE kraj = ? AND cena <= ?");
mysqli_stmt_bind_param($stmt, "sd", $kraj, $cenaMax);
mysqli_stmt_execute($stmt);
$wynik = mysqli_stmt_get_result($stmt);
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<li>" . htmlspecialchars($wiersz["nazwa"]) . " – {$wiersz['cena']} zł</li>";
}
mysqli_stmt_close($stmt);
mysqli_close($polaczenie);
ZagrożenieOpisZabezpieczenie w PHP
SQL Injectiondane użytkownika zmieniają strukturę zapytaniazapytania przygotowane (mysqli_prepare, PDO), rzutowanie liczb (intval), konto bazy z minimalnymi uprawnieniami
XSS (Cross-Site Scripting)wstrzyknięty skrypt wykonuje się w przeglądarce innych użytkownikówhtmlspecialchars($tekst) przy wypisywaniu danych od użytkownika
CSRFfałszywe żądanie wykonane w imieniu zalogowanego użytkownikatoken CSRF w formularzach (bin2hex(random_bytes(16)) w sesji), SameSite
jawne hasłahasło w bazie lub kodzie można odczytać po wyciekupassword_hash(), password_verify()
przejęcie sesjikradzież identyfikatora sesjiHTTPS, session_regenerate_id(true) po zalogowaniu, flagi HttpOnly/Secure
niebezpieczne plikiwysłanie skryptu zamiast obrazusprawdzenie typu mime_content_type, własna nazwa pliku, katalog bez wykonywania PHP
wyciek informacjikomunikaty błędów pokazują ścieżki i zapytaniaw produkcji display_errors = Off, błędy do logu
Hasła i walidacja
// zapis hasła do bazy (rejestracja)
$hash = password_hash($_POST["haslo"], PASSWORD_DEFAULT);      // wynik zapisz w kolumnie VARCHAR(255)

// sprawdzenie przy logowaniu
if (password_verify($_POST["haslo"], $wiersz["haslo_hash"])) {
    session_regenerate_id(true);
    $_SESSION["uzytkownik"] = $wiersz["login"];
}

// walidacja danych wejściowych
$email = filter_var($_POST["email"], FILTER_VALIDATE_EMAIL);   // false, gdy niepoprawny
$wiek = filter_var($_POST["wiek"], FILTER_VALIDATE_INT, ["options" => ["min_range" => 16, "max_range" => 99]]);
Egzamin a praktyka

Na egzaminie punkty dostajesz za zgodność z arkuszem – prosty zapis WHERE id = $id z intval wystarcza. Pamiętaj jednak, że w projekcie, pracy i na wywiadzie technicznym oczekuje się zapytań przygotowanych i htmlspecialchars. Ćwicz oba style.

Sprawdź się — zadanie 1

Na czym polega atak SQL Injection i jak mu zapobiec?

Pokaż przykładową odpowiedź

Atakujący wpisuje w pole formularza fragment SQL, który zmienia sens zapytania. Zapobiega się mu zapytaniami przygotowanymi z parametrami (dane nie są częścią polecenia SQL), walidacją i rzutowaniem oraz kontem bazy z minimalnymi uprawnieniami.

Sprawdź się — zadanie 2

Którą funkcją zabezpieczysz tekst od użytkownika przed wykonaniem jako HTML przy wyświetlaniu?

Pokaż przykładową odpowiedź

htmlspecialchars() – zamienia znaki specjalne (< > & " ') na encje.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — popraw kod

Identyfikator z adresu jest wklejany do zapytania bez żadnej kontroli, co umożliwia SQL Injection. Zabezpiecz go rzutowaniem na liczbę całkowitą.

Ćwiczenie interaktywne — uzupełnij kod

Wypisz komentarz od użytkownika tak, aby znaczniki HTML nie zostały wykonane (ochrona przed XSS).

echo "<p>Komentarz: " . ($_POST["komentarz"]) . "</p>";
Ćwiczenie interaktywne — uzupełnij kod

Zapisz hasło jako skrót przy rejestracji i sprawdź je przy logowaniu.

$hash = ($haslo, PASSWORD_DEFAULT);
if (($wpisane, $hash)) {
    echo "Poprawne hasło";
}
Ćwiczenie interaktywne — popraw kod

Zapytanie przygotowane nadal wkleja wartość do SQL zamiast użyć znaku zapytania. Popraw zapytanie tak, aby wartość była przekazywana przez mysqli_stmt_bind_param.

Część C, lekcja 17

Debugowanie i typowe błędy w PHP

Komunikat / objawPrzyczynaRozwiązanie
Parse error: syntax error, unexpected ...brak średnika, nawiasu lub cudzysłowu; zły cudzysłów wewnątrz echosprawdź linię z komunikatu i poprzednią; użyj apostrofów w atrybutach HTML
Undefined variable $xliterówka w nazwie zmiennej lub zmienna poza zakresem funkcjisprawdź pisownię i wielkość liter; w funkcji przekaż zmienną parametrem
Undefined array key "nazwa"kolumny nie ma w wyniku zapytania (zapytanie ich nie wybiera lub inna pisownia)dopisz kolumnę do SELECT; sprawdź print_r($wiersz)
Warning: Undefined array key "id" dla $_GETstrona otwarta bez parametruużyj isset($_GET['id']) lub ??
mysqli_connect(): (HY000/1049): Unknown databasezła nazwa bazy lub baza nie została utworzonasprawdź nazwę w phpMyAdmin i literówki
Access denied for user 'root'@'localhost'złe hasło lub użytkownikna egzaminie: użytkownik root, hasło puste
mysqli_fetch_array() expects parameter 1 to be mysqli_result, bool givenzapytanie zwróciło false (błąd SQL)wypisz mysqli_error($polaczenie) i popraw zapytanie
Cannot modify header information – headers already sentwyjście przed header()/session_start()przenieś kod na początek pliku; usuń spacje i puste linie przed <?php
pusta strona (biała)błąd krytyczny przy wyłączonym wyświetlaniu błędówwłącz display_errors lub zajrzyj do error.log
„krzaczki” zamiast polskich literzłe kodowanie pliku lub połączeniaplik UTF-8, <meta charset="UTF-8">, mysqli_set_charset($p, "utf8"), baza w utf8_unicode_ci
tabela pusta mimo zapytaniawarunek nie pasuje do danych lub zły parametr GETwykonaj to samo zapytanie w phpMyAdmin i porównaj
obrazy się nie ładujązła ścieżka lub wielkość liter w nazwie plikusprawdź kod źródłowy strony (Ctrl+U) i kartę Network
Narzędzia diagnostyczne
<?php
// 1. pokaż wszystkie błędy podczas pracy (na początku pliku)
ini_set("display_errors", "1");
error_reporting(E_ALL);

// 2. zobacz zawartość zmiennych i tablic
$wiersz = mysqli_fetch_array($wynik);
echo "<pre>"; print_r($wiersz); echo "</pre>";
var_dump($_GET);                                   // typ i wartość

// 3. sprawdź zapytanie, które wysyłasz do bazy
echo $zapytanie;                                   // skopiuj do phpMyAdmin i uruchom

// 4. błąd bazy
if (!$wynik) { echo mysqli_error($polaczenie); }
  • Ctrl+U w przeglądarce pokazuje źródło HTML wygenerowane przez PHP – widać, czy pętla wypisała elementy i czy atrybuty mają poprawne wartości.
  • Logi Apache: xampp/apache/logs/error.log; konfiguracja PHP: php.ini (po zmianie restartuj Apache).
  • Testuj strony małymi krokami: najpierw samo połączenie, potem jedno zapytanie z print_r, dopiero potem formatowanie HTML.
  • Po zakończeniu pracy usuń komendy debugujące (print_r, var_dump) i wyłącz wyświetlanie błędów, jeśli arkusz tego nie wymaga.
Sprawdź się — zadanie 1

Co oznacza błąd „headers already sent” i jak go naprawić?

Pokaż przykładową odpowiedź

Skrypt wysłał już treść do przeglądarki (np. HTML lub spację), a próbuje użyć header() lub session_start(). Należy przenieść ten kod na sam początek pliku, przed jakiekolwiek wyjście.

Sprawdź się — zadanie 2

Jak sprawdzić, czy zapytanie SQL ze skryptu jest poprawne?

Pokaż przykładową odpowiedź

Wypisać jego treść (echo $zapytanie) lub błąd (mysqli_error()) i wykonać je w phpMyAdmin.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — popraw kod

Skrypt zgłasza błąd Parse error: w pierwszej linii brakuje średnika. Popraw kod.

Ćwiczenie interaktywne — popraw kod

Napis z zapytaniem SQL nie jest zamknięty cudzysłowem, a wywołanie funkcji nawiasem. Popraw instrukcję.

Ćwiczenie interaktywne — popraw kod

W pętli użyto nazwy zmiennej z literówką i nieistniejącego klucza tablicy (nazwy kolumn zapisujemy tak jak w bazie). Popraw kod.

Ćwiczenie interaktywne — uzupełnij kod

Włącz wyświetlanie wszystkich błędów i sprawdź zawartość zmiennej podczas debugowania.

(E_ALL);
ini_set("", 1);
($wiersz);

Część C, lekcja 18

Zadanie wzorcowe 1: portal turystyczny (galeria i opis)

To rozwiązanie jest wzorowane na zadaniu z arkusza „portal turystyczny” (dwie strony, wspólny boczny blok, galeria miniatur i opis wybranego miejsca), ale na własnych danych. Przygotuj bazę podroze i obrazy z podroze.zip.

Wymagania (skrócone)

  • Strony index.php i atrakcja.php; HTML5, lang="pl", UTF-8, tytuł „Atlas atrakcji”, styl.css.
  • Bloki: nagłówkowy, główny (z blokiem sekcji), boczny, stopka – układ jak na ilustracji (nagłówek i główny w lewej kolumnie, boczny po prawej).
  • Blok boczny: nagłówek h3 „Polecane”, lista punktowana z dwoma elementami: „Parki narodowe:” i „Plaże:”, każdy z listą numerowaną z zapytań (skrypty 1 i 2 na obu stronach).
  • Strona główna: nagłówek h2 „Galeria” i miniatury wszystkich atrakcji z Polski i Hiszpanii jako obrazy-odnośniki do atrakcja.php?id=… z dymkiem (skrypt 3).
  • Strona atrakcja.php: nagłówek h2 „Opis miejsca”; po parametrze GET: obraz, h2 z nazwą, h3 z kategorią, akapit „Cena: … zł, ocena: …” i akapit z opisem (skrypt 4).
index.php
<?php
$polaczenie = mysqli_connect("localhost", "root", "", "podroze");
mysqli_set_charset($polaczenie, "utf8");
?>
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Atlas atrakcji</title>
    <link rel="stylesheet" href="styl.css">
</head>
<body>
    <div id="strona">
        <div id="kolumna">
            <header>
                <h1><a href="index.php">Odkrywaj świat</a></h1>
            </header>
            <main>
                <h2>Galeria</h2>
                <section>
                    <?php
                    // skrypt 3: miniatury jako obrazy-odnośniki
                    $wynik = mysqli_query($polaczenie, "SELECT idAtrakcja, plik, nazwa FROM atrakcje WHERE kraj = 'Polska' OR kraj = 'Hiszpania';");
                    while ($wiersz = mysqli_fetch_array($wynik)) {
                        echo "<a href='atrakcja.php?id={$wiersz['idAtrakcja']}'>
                                <img class='miniatura' src='img/{$wiersz['plik']}' alt='{$wiersz['nazwa']}' title='{$wiersz['nazwa']}'>
                              </a>";
                    }
                    ?>
                </section>
            </main>
        </div>
        <aside>
            <h3>Polecane</h3>
            <ul>
                <li>Parki narodowe:
                    <ol>
                        <?php
                        // skrypt 1
                        $wynik = mysqli_query($polaczenie, "SELECT nazwa FROM atrakcje WHERE idKategoria = 2;");
                        while ($wiersz = mysqli_fetch_array($wynik)) {
                            echo "<li>{$wiersz['nazwa']}</li>";
                        }
                        ?>
                    </ol>
                </li>
                <li>Plaże:
                    <ol>
                        <?php
                        // skrypt 2: zapytanie 1 zmodyfikowane – inna kategoria
                        $wynik = mysqli_query($polaczenie, "SELECT nazwa FROM atrakcje WHERE idKategoria = 3;");
                        while ($wiersz = mysqli_fetch_array($wynik)) {
                            echo "<li>{$wiersz['nazwa']}</li>";
                        }
                        ?>
                    </ol>
                </li>
            </ul>
        </aside>
    </div>
    <footer>
        <hr>
        <p>Autor: 12345</p>
    </footer>
    <?php mysqli_close($polaczenie); ?>
</body>
</html>
atrakcja.php (część różniąca się od index.php)
<?php
$polaczenie = mysqli_connect("localhost", "root", "", "podroze");
mysqli_set_charset($polaczenie, "utf8");
?>
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Atlas atrakcji</title>
    <link rel="stylesheet" href="styl.css">
</head>
<body>
    <div id="strona">
        <div id="kolumna">
            <header>
                <h1><a href="index.php">Odkrywaj świat</a></h1>
            </header>
            <main>
                <h2>Opis miejsca</h2>
                <section>
                    <?php
                    // skrypt 4: zapytanie z parametrem GET
                    if (isset($_GET["id"])) {
                        $id = intval($_GET["id"]);
                        $wynik = mysqli_query($polaczenie,
                            "SELECT plik, atrakcje.nazwa, kategorie.nazwa AS kategoria, cena, ocena, opis
                             FROM atrakcje JOIN kategorie USING(idKategoria)
                             WHERE idAtrakcja = $id;");
                        $wiersz = mysqli_fetch_array($wynik);
                        if ($wiersz) {
                            echo "<img src='img/{$wiersz['plik']}' alt='{$wiersz['nazwa']}'>";
                            echo "<h2>{$wiersz['nazwa']}</h2>";
                            echo "<h3>{$wiersz['kategoria']}</h3>";
                            echo "<p>Cena: {$wiersz['cena']} zł, ocena: {$wiersz['ocena']}</p>";
                            echo "<p>{$wiersz['opis']}</p>";
                        }
                    }
                    ?>
                </section>
            </main>
        </div>
        <aside>
            <!-- identyczny blok boczny jak w index.php (skrypty 1 i 2) -->
        </aside>
    </div>
    <footer><hr><p>Autor: 12345</p></footer>
    <?php mysqli_close($polaczenie); ?>
</body>
</html>
styl.css
* { font-family: "Trebuchet MS", sans-serif; }
body { margin: 0; }
#strona { display: flex; }                      /* kolumna po lewej, aside po prawej */
#kolumna { width: 70%; }
header, aside { background-color: PaleTurquoise; border-radius: 20px; }
header { height: 100px; padding: 2%; text-align: center; }
header a { color: #134e4a; text-decoration: none; }
main { text-align: center; }
aside { width: 25%; height: 700px; line-height: 2; margin-left: 2%; }
section {
    border: 1px dashed DimGray;
    margin: 10px 50px;
    height: 400px;
    overflow: auto;                              /* paski przewijania tylko przy przepełnieniu */
}
.miniatura { height: 200px; padding: 10px; opacity: 0.8; transition: opacity 1s; }
.miniatura:hover { opacity: 1; }
footer { padding-top: 10px; }
section img:not(.miniatura) { max-width: 60%; }
Czy zapytania muszą się powtarzać na obu stronach

Arkusz wymaga skryptów „na obu stronach”. Możesz zapisać blok boczny w pliku boczny.php i dołączyć go przez require w obu plikach – ale połączenie z bazą (mysqli_connect) i jego zamknięcie muszą pozostać widoczne w każdym pliku strony, aby komisja mogła ocenić kryteria w kodzie. Bezpieczniej jest powtórzyć kod.

  • Obrazy-odnośniki: title daje dymek, alt – tekst alternatywny; klasa miniatura ma transition: opacity 1s i zmianę opacity przy :hover.
  • Zapytanie 4 po JOIN ... USING(idKategoria) ma kolumnę nazwa w obu tabelach – dlatego użyto atrakcje.nazwa i aliasu kategoria dla drugiej.
  • Jeżeli parametr id jest niepoprawny, mysqli_fetch_array zwróci null – warunek if ($wiersz) zapobiega błędom.
Sprawdź się — zadanie 1

Dlaczego w zapytaniu z JOIN trzeba użyć nazwy tabeli przy kolumnie nazwa?

Pokaż przykładową odpowiedź

Bo kolumna o tej nazwie występuje w obu tabelach i baza zgłosi błąd niejednoznaczności; trzeba jej użyć w postaci atrakcje.nazwa, a drugą kolumnę nazwać aliasem.

Sprawdź się — zadanie 2

Co zapewnia pole title w obrazie-odnośniku?

Pokaż przykładową odpowiedź

Wyświetlenie dymku z nazwą po najechaniu kursorem.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Strona główna: wybierz atrakcje z Polski lub Hiszpanii i zbuduj odnośniki do strony atrakcja.php.

$wynik = mysqli_query($polaczenie, "SELECT idAtrakcja, plik, nazwa FROM atrakcje WHERE kraj = 'Polska'  kraj = 'Hiszpania';");
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<a href='atrakcja.php?id={$wiersz['']}'><img class='miniatura' src='img/{$wiersz['plik']}' alt='{$wiersz['nazwa']}'></a>";
}
Ćwiczenie interaktywne — uzupełnij kod

Strona atrakcja.php: pobierz atrakcję wraz z nazwą jej kategorii i wypisz nagłówki.

$id = intval($_GET["id"] ?? 1);
$wynik = mysqli_query($polaczenie, "SELECT atrakcje.nazwa, atrakcje.cena, kategorie.nazwa AS kategoria FROM atrakcje  kategorie ON atrakcje.idKategoria = kategorie.idKategoria WHERE atrakcje.idAtrakcja = $id;");
$wiersz = mysqli_fetch_array($wynik);
echo "<h2>{$wiersz['nazwa']}</h2><h3>{$wiersz['']}</h3>";
Ćwiczenie interaktywne — popraw kod

Wyniki zapytania mają kolumny „nazwa” i alias „kategoria”, a skrypt odwołuje się do nazw z prefiksem tabeli. Popraw odczyt pól.

Ćwiczenie interaktywne — popraw kod

W arkuszu stylów brakuje średnika i dwukropka. Popraw regułę dla miniatur.

Część C, lekcja 19

Zadanie wzorcowe 2: witryna z tabelą, filtrowaniem i zapisem formularza

Wzorzec z arkusza „witryna hobbystyczna”: odnośniki do regionów po lewej, tabela dla wybranego regionu, formularz zapisujący rekord oraz blok z obrazami. Baza podroze (podroze.zip).

index.php
<?php
$polaczenie = mysqli_connect("localhost", "root", "", "podroze");
mysqli_set_charset($polaczenie, "utf8");

// skrypt 4: zapis wizyty (na początku pliku, przed HTML – dzięki temu działa przekierowanie)
if (isset($_POST["wpisz"])) {
    $atrakcja = intval($_POST["atrakcja"]);
    $turysta = intval($_POST["turysta"]);
    mysqli_query($polaczenie, "INSERT INTO wizyty (idAtrakcja, idTurysta, dataWizyty) VALUES ($atrakcja, $turysta, CURDATE());");
    header("Location: index.php");
    exit();
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Atrakcje świata</title>
    <link rel="stylesheet" href="styl.css">
</head>
<body>
    <header>
        <h2>Łowcy atrakcji</h2>
    </header>
    <main>
        <aside>
            <?php
            // skrypt 1: odnośniki do tej samej strony z parametrem GET
            $wynik = mysqli_query($polaczenie, "SELECT idRegion, nazwa FROM regiony;");
            while ($wiersz = mysqli_fetch_array($wynik)) {
                echo "<a href='index.php?idRegion={$wiersz['idRegion']}'>{$wiersz['nazwa']}</a>";
            }
            ?>
        </aside>
        <section>
            <table>
                <tr><th>Identyfikator</th><th>Kraj</th><th>Atrakcja</th><th>Cena</th></tr>
                <?php
                // skrypt 2: parametr GET lub wartość domyślna 1
                $region = isset($_GET["idRegion"]) ? intval($_GET["idRegion"]) : 1;
                $wynik = mysqli_query($polaczenie, "SELECT idAtrakcja, kraj, nazwa, cena FROM atrakcje WHERE idRegion = $region;");
                while ($wiersz = mysqli_fetch_array($wynik)) {
                    echo "<tr>
                            <td>{$wiersz['idAtrakcja']}</td>
                            <td>{$wiersz['kraj']}</td>
                            <td>{$wiersz['nazwa']}</td>
                            <td>{$wiersz['cena']}</td>
                          </tr>";
                }
                ?>
            </table>
            <h4>Zapisz swoją wizytę</h4>
            <form action="index.php" method="post">
                <label for="atrakcja">identyfikator atrakcji</label>
                <input type="number" name="atrakcja" id="atrakcja" min="1">
                <label for="turysta">turysta</label>
                <select name="turysta" id="turysta">
                    <?php
                    // skrypt 3: lista rozwijana
                    $wynik = mysqli_query($polaczenie, "SELECT idTurysta, nick FROM turysci;");
                    while ($wiersz = mysqli_fetch_array($wynik)) {
                        echo "<option value='{$wiersz['idTurysta']}'>{$wiersz['nick']}</option>";
                    }
                    ?>
                </select>
                <button type="submit" name="wpisz">Wpisz</button>
            </form>
        </section>
    </main>
    <article>
        <h3>Atrakcje w Polsce</h3>
        <?php
        $wynik = mysqli_query($polaczenie, "SELECT plik FROM atrakcje WHERE kraj = 'Polska';");
        while ($wiersz = mysqli_fetch_array($wynik)) {
            echo "<img src='img/{$wiersz['plik']}' alt='atrakcja'>";
        }
        ?>
    </article>
    <footer>
        <p>Autor: 12345</p>
    </footer>
    <?php mysqli_close($polaczenie); ?>
</body>
</html>
styl.css
* { font-family: Garamond, serif; text-align: center; }
header, footer { background-color: SkyBlue; padding: 8px; margin: 10px; border-radius: 20px; }
main { display: flex; }
aside { width: 30%; }
section { width: 70%; height: 300px; overflow: auto; }
article { background-color: AliceBlue; height: 280px; padding: 8px; margin: 10px; border-radius: 20px; }
img { margin: 4px; float: left; width: 133px; }                       /* obrazy obok siebie od lewej */
table { width: 90%; border: 1px dotted black; }
a {
    display: block;
    background-color: DodgerBlue;
    color: white;
    width: 65%;
    font-size: 130%;
    margin: 10px;
    text-decoration: none;
}
a:hover { background-color: RoyalBlue; }
  • Styl tylko selektorem znacznika (img, table, a) – tak wymaga uwaga w arkuszu („uwarunkowane projektem późniejszej rozbudowy”); nie dopisuj klas do tych znaczników.
  • Pole liczbowe: type="number"; lista rozwijana wypełniona skryptem; przycisk wysyła formularz metodą post do tego samego pliku.
  • float: left na obrazach i text-align: center w * to typowe wymagania „obok siebie od lewej strony” i „wyrównanie do środka”.
  • Skrypt zapisujący formularz jest na początku pliku – po sukcesie następuje przekierowanie, a odświeżenie strony nie dubluje wizyty.
Sprawdź się — zadanie 1

Dlaczego kod obsługujący formularz warto umieścić na początku pliku, przed HTML?

Pokaż przykładową odpowiedź

Aby po zapisie można było użyć header("Location: ..."), które wymaga, by przed nim nie było żadnego wyjścia.

Sprawdź się — zadanie 2

Jak wypełnić listę rozwijaną rekordami z bazy?

Pokaż przykładową odpowiedź

W pętli po wyniku zapytania wypisać <option value='{id}'>{nick}</option> wewnątrz <select>.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Wypisz listę rozwijaną z nazwami krajów bez powtórzeń (wartość opcji to nazwa kraju).

$wynik = mysqli_query($polaczenie, "SELECT  kraj FROM atrakcje ORDER BY kraj;");
echo "<select name='kraj'>";
while ($wiersz = mysqli_fetch_array($wynik)) {
    echo "<option value='{$wiersz['kraj']}'>{$wiersz['kraj']}</option>";
}
echo "</select>";
Ćwiczenie interaktywne — uzupełnij kod

Po wysłaniu formularza odczytaj wybrany kraj i wypisz tabelę atrakcji z tego kraju.

if (isset($_["kraj"])) {
    $kraj = $_POST["kraj"];
    $wynik = mysqli_query($polaczenie, "SELECT nazwa, cena FROM atrakcje WHERE kraj = '';");
    echo "<table>";
    while ($wiersz = mysqli_fetch_array($wynik)) {
        echo "<tr><td>{$wiersz['nazwa']}</td><td>{$wiersz['cena']}</td></tr>";
    }
    echo "</table>";
}
Ćwiczenie interaktywne — popraw kod

Formularz zapisuje wizytę, ale skrypt odczytuje dane po złych nazwach pól i nie sprawdza, czy formularz został wysłany. Popraw kod (pola formularza: atrakcja, turysta, data).

Ćwiczenie interaktywne — uzupełnij kod

Wypisz średnią cenę atrakcji w kraju, zaokrągloną do dwóch miejsc po przecinku.

$wynik = mysqli_query($polaczenie, "SELECT (cena) FROM atrakcje WHERE kraj = 'Polska';");
$wiersz = mysqli_fetch_row($wynik);
echo "Średnia cena: " . ($wiersz[0], 2) . " zł";

Część C, lekcja 20

Zadanie wzorcowe 3: logowanie i raport z LEFT JOIN

Wzorzec z arkusza „portal laboratorium”: logowanie z sesją, strona dostępna tylko po zalogowaniu z tabelą wyników (LEFT JOIN, odnośniki do plików) i wylogowanie. Baza biblioteka z biblioteka.zip (konto: login admin, hasło TajneHaslo321*).

login.php
<?php
session_start();
$komunikat = "";
if (isset($_POST["zaloguj"])) {
    if ($_POST["login"] == "admin" && $_POST["haslo"] == "TajneHaslo321*") {
        $_SESSION["uzytkownik"] = "admin";
        header("Location: wyniki.php");
        exit();
    }
    $komunikat = "Błędny login lub hasło.";
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Zaloguj się</title>
    <link rel="stylesheet" href="styl.css">
</head>
<body>
    <header><h1>Portal biblioteki</h1></header>
    <main id="logowanie">
        <h2>Zaloguj się do systemu</h2>
        <form action="login.php" method="post">
            <label for="login">Login:</label>
            <input type="text" name="login" id="login"><br>
            <label for="haslo">Hasło:</label>
            <input type="password" name="haslo" id="haslo"><br>
            <button type="submit" name="zaloguj">Zaloguj</button>
        </form>
        <p><?php echo $komunikat; ?></p>
    </main>
    <footer><p>Wykonał: 12345</p></footer>
</body>
</html>
wyniki.php
<?php
session_start();
if (!isset($_SESSION["uzytkownik"])) {
    header("Location: login.php");
    exit();
}
$polaczenie = mysqli_connect("localhost", "root", "", "biblioteka");
mysqli_set_charset($polaczenie, "utf8");
?>
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Przeglądaj wypożyczenia</title>
    <link rel="stylesheet" href="styl.css">
</head>
<body>
    <header>
        <h1>Portal biblioteki</h1>
        <p>Witaj, <strong><?php echo $_SESSION["uzytkownik"]; ?></strong></p>
        <p><a href="wyloguj.php">Wyloguj</a></p>
    </header>
    <main>
        <h2>Wypożyczenia</h2>
        <div id="tabela">
            <table>
                <tr><th>Imię</th><th>Nazwisko</th><th>Tytuł</th><th>Data wypożyczenia</th><th>Data zwrotu</th><th>Potwierdzenie</th></tr>
                <?php
                $zapytanie = "SELECT czytelnicy.imie, czytelnicy.nazwisko, ksiazki.tytul, wypozyczenia.dataWypozyczenia, zwroty.dataZwrotu, zwroty.potwierdzenie
                              FROM wypozyczenia
                              JOIN czytelnicy ON wypozyczenia.idCzytelnik = czytelnicy.idCzytelnik
                              JOIN ksiazki ON wypozyczenia.idKsiazka = ksiazki.idKsiazka
                              LEFT JOIN zwroty ON zwroty.idWypozyczenie = wypozyczenia.idWypozyczenie
                              ORDER BY wypozyczenia.dataWypozyczenia;";
                $wynik = mysqli_query($polaczenie, $zapytanie);
                while ($wiersz = mysqli_fetch_array($wynik)) {
                    echo "<tr>
                            <td>{$wiersz['imie']}</td>
                            <td>{$wiersz['nazwisko']}</td>
                            <td>{$wiersz['tytul']}</td>
                            <td>{$wiersz['dataWypozyczenia']}</td>
                            <td>{$wiersz['dataZwrotu']}</td>
                            <td>";
                    if (!empty($wiersz['potwierdzenie'])) {
                        echo "<a href='{$wiersz['potwierdzenie']}' target='_blank'>potwierdzenie</a>";
                    }
                    echo "</td></tr>";
                }
                mysqli_close($polaczenie);
                ?>
            </table>
        </div>
    </main>
    <footer><p>Wykonał: 12345</p></footer>
</body>
</html>
wyloguj.php
<?php
session_start();
if (isset($_SESSION["uzytkownik"])) {
    unset($_SESSION["uzytkownik"]);
    session_destroy();
}
header("Location: login.php");
exit();
styl.css (fragment)
body { font-family: Verdana, sans-serif; background-color: #D4F1F4; color: #05445E; }
header, #tabela, #logowanie {
    background-color: #75E6DA;
    border-radius: 5px;
    box-shadow: 10px 10px 5px LightBlue;     /* przesunięcie poziome, pionowe, rozmycie, kolor */
}
header { margin: 40px; }
#tabela { margin: 40px; padding: 20px; }
#logowanie { width: 50em; margin: 20px auto; padding: 20px; }   /* auto = środek */
h1, h2, form { text-align: center; }
table { margin: auto; border-collapse: collapse; }
table, tr { border: 1px solid #189AB4; }
th, td { padding: 10px; }
tr:hover { background-color: rgba(255, 255, 255, 0.8); }
a { text-decoration: none; }
footer { text-align: right; }
Kryterium arkuszaGdzie w kodzie
„jeśli dane z formularza, login admin i hasło …, ustawiana zmienna sesji i ładowana strona wyniki.php”login.php: $_SESSION[...] i header("Location: wyniki.php")
„jeśli zmienna sesji nie jest ustawiona – przekierowanie do login.php”początek wyniki.php: isset + header + exit
„Witaj, <użytkownik>” w znaczniku semantycznym ważnego tekstu<strong>
„wszystkie zlecenia niezależnie od tego, czy mają wynik”LEFT JOIN zwroty
„gdy nazwa pliku niepusta – odnośnik w nowej karcie”if (!empty(...)) echo "<a ... target='_blank'>"
„ostatnia operacja skryptu – zamknięcie połączenia”mysqli_close($polaczenie) po pętli
„dane sesji są czyszczone, przekierowanie do login.php”wyloguj.php
Pułapka z zaznaczaniem pola i pustymi datami

W kolumnie „Data zwrotu” dla wypożyczeń bez zwrotu wartość to NULL – echo wypisze pusty napis. To poprawne; opcjonalnie wypisz „—”. Nigdy nie opierasz zabezpieczenia strony na ukryciu odnośnika: sprawdzenie sesji na początku wyniki.php jest obowiązkowe.

Sprawdź się — zadanie 1

Jak działa wylogowanie w arkuszu z sesją?

Pokaż przykładową odpowiedź

Skrypt usuwa zmienną sesyjną z nazwą użytkownika i kończy sesję (unset, session_destroy), a następnie przekierowuje do strony logowania.

Sprawdź się — zadanie 2

Dlaczego strona wyniki.php musi sprawdzić zmienną sesji na samym początku?

Pokaż przykładową odpowiedź

Aby niezalogowany użytkownik został od razu przekierowany i nie zobaczył żadnej treści ani danych; sprawdzenie musi poprzedzać wyjście HTML i zapytania do bazy.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — uzupełnij kod

Sprawdź login i hasło w tabeli uzytkownicy; po poprawnym zalogowaniu zapisz login w sesji.

session_start();
$sql = "SELECT login FROM uzytkownicy WHERE login = '$login' AND haslo = '$haslo';";
$wynik = mysqli_query($polaczenie, $sql);
if (mysqli_num_rows($wynik)  1) {
    $_SESSION[""] = $login;
    header("Location: wyniki.php");
    exit;
}
Ćwiczenie interaktywne — uzupełnij kod

Wypisz wszystkich czytelników biblioteki i daty ich wypożyczeń, także czytelników bez wypożyczeń.

SELECT czytelnicy.nazwisko, wypozyczenia.dataWypozyczenia
FROM czytelnicy
 wypozyczenia ON czytelnicy.idCzytelnik = wypozyczenia.idCzytelnik
ORDER BY czytelnicy.nazwisko;
Ćwiczenie interaktywne — popraw kod

Skrypt wylogowania nie kończy sesji: brakuje jej uruchomienia przed zniszczeniem i przekierowania. Popraw kod.

Ćwiczenie interaktywne — popraw kod

Wypisanie daty wypożyczenia powoduje komunikat o błędzie, gdy czytelnik nie ma wypożyczeń (wartość null). Dodaj zamianę pustej wartości na tekst „brak”.

Część C, lekcja 21

Lista kontrolna i strategia egzaminu z PHP

Przed napisaniem pierwszej linii

  • Przeczytaj cały arkusz (10 minut na zapoznanie nie wlicza się do czasu) i zaznacz liczby, nazwy plików, teksty i kolory.
  • Sprawdź, jaka jest nazwa bazy, tabel i kolumn (rysunek bazy w arkuszu) – wszystkie nazwy kopiuj z arkusza.
  • Zapisz kolejność prac: baza → kwerendy → HTML → CSS → skrypty → zrzuty/porządki → test w przeglądarce.

Lista kontrolna wg rezultatów

RezultatSprawdź
R.1 bazanazwa bazy i zestaw znaków zgodne z poleceniem; zrzut import (cały ekran); kwerendy.txt z każdą kwerendą; zrzuty kw1… z poprawnymi nazwami i formatem
R.2 zawartość<!DOCTYPE html>, lang="pl", charset UTF-8, tytuł strony, bloki semantyczne (header, main, section, aside, article, footer), nagłówki o właściwych stopniach, tabele z th, formularz z etykietami, obrazy z alt, numer zdającego w stopce
R.3 działanieukład bloków identyczny jak na ilustracji; połączony styl.css; odnośniki prowadzą do właściwych stron; formularz wysyła metodą POST do właściwego pliku
R.4 CSSkażda wartość z arkusza (kolory, marginesy, szerokości, border-radius, czcionki); efekty :hover; style wyłącznie z styl.css; przestrzeganie uwag o selektorach
R.5 skryptypołączenie i mysqli_close; zapytania zgodne z poleceniem; pętle po wynikach; poprawne HTML w echo; obsługa GET/POST/sesji; brak błędów PHP

Test końcowy

  • Otwórz stronę przez http://localhost/… w jednej przeglądarce (tej, której nazwę zapisujesz w przegladarka.txt) i przejdź wszystkie ścieżki: kliknij każdy odnośnik, wyślij formularz, zaloguj się i wyloguj.
  • Przejrzyj źródło (Ctrl+U): czy tytuł, nagłówki i teksty są takie jak w arkuszu.
  • Sprawdź, czy w folderze zdającego są wszystkie wymagane pliki (arkusz wymienia je na końcu) i obrazy w katalogach, do których odwołują się strony.
  • Skopiuj aktualne pliki z htdocs do folderu zdającego (po ostatniej zmianie!).
Dobre praktyki egzaminacyjne

Nie zmieniaj nazw plików ani klas z arkusza, nie dodawaj elementów, których arkusz nie wymaga, zapisuj pliki często (Ctrl+S) i odświeżaj stronę po każdej większej zmianie (Ctrl+F5). Jeśli czegoś nie potrafisz zrobić – zostaw to i wróć; nie blokuj się na jednym kryterium.

Sprawdź się — zadanie 1

Co najważniejszego należy sprawdzić w pliku HTML pod kątem rezultatu R.2?

Pokaż przykładową odpowiedź

Deklarację HTML5 i język (lang="pl"), kodowanie UTF-8, tytuł strony, strukturę semantyczną, nagłówki i teksty dokładnie jak w arkuszu, tabele, formularz z etykietami, obrazy z alt, stopkę z numerem zdającego.

Sprawdź się — zadanie 2

Dlaczego na końcu pracy kopiuje się pliki z htdocs do folderu zdającego?

Pokaż przykładową odpowiedź

Aby w folderze do oceny były aktualne wersje wszystkich plików; pracując w htdocs, łatwo zostawić tam nowsze wersje niż w folderze z rezultatami.

Ćwiczenia interaktywne: uzupełnij i popraw kod

Wpisz brakujące fragmenty w polach albo popraw kod w oknie i kliknij „Sprawdź”. Białe znaki i rodzaj cudzysłowu nie mają znaczenia. Rozwiązane ćwiczenia zapisują się na koncie i liczą się do odznak.

Ćwiczenie interaktywne — popraw kod

Skrypt ma trzy typowe błędy z egzaminu: elementy tablicy w napisie bez klamer, brak zamknięcia znacznika listy i niepoprawne pobieranie wiersza. Popraw kod tak, aby wypisał listę atrakcji z cenami.

Ćwiczenie interaktywne — uzupełnij kod

Uzupełnij szkielet strony zgodnie z typowymi wymaganiami arkusza: HTML5, język polski, kodowanie UTF-8.


<html lang="">
<head>
    <meta charset="">
    <title>Atlas atrakcji</title>
    <link rel="stylesheet" href="styl.css">
</head>
Ćwiczenie interaktywne — popraw kod

Zapytanie nie zwraca wyniku, bo sortowanie zapisano niepoprawnie, a warunek na cenę porównuje liczbę jak tekst. Popraw zapytanie SQL.

Zadanie praktyczne

Portal biblioteki: logowanie, raport i zaległości

Szacowany czas: 90 minZakres: Lekcje 11–18Forma: kompletna witryna w folderze z numerem

Zadanie

Na bazie biblioteka (biblioteka.zip) wykonaj witrynę z logowaniem, tabelą wypożyczeń i raportem zaległości.

Założenia

  • Baza: import, zrzut import, zapytania zapisane w kwerendy.txt: (1) imię i nazwisko czytelników malejąco po nazwisku, (2) liczba wypożyczeń w każdym miesiącu (MONTH), (3) tytuły książek wypożyczonych przez czytelnika o nazwisku Maj (relacja), (4) wypożyczenia wraz z datą zwrotu i potwierdzeniem – także te bez zwrotu (LEFT JOIN).
  • Strony login.php, wypozyczenia.php i wyloguj.php zgodnie ze wzorcem z lekcji 20 (logowanie admin / TajneHaslo321*, sesja, przekierowania).
  • Na stronie wypozyczenia.php: tabela z kolumnami Imię, Nazwisko, Tytuł, Data wypożyczenia, Data zwrotu (dla braku zwrotu napis „niezwrócona”), Potwierdzenie (odnośnik w nowej karcie, gdy plik istnieje).
  • Dodatkowo blok „Zaległe”: lista numerowana tytułów książek niezwróconych (zapytanie z LEFT JOIN i warunkiem zwroty.idZwrot IS NULL) oraz napis „Liczba zaległych: N”.
  • Formularz „Dodaj wypożyczenie” (lista czytelników, lista książek, przycisk) wstawiający rekord z datą CURDATE() i przekierowujący z powrotem.
  • CSS w styl.css: kolory, tabela z połączonym obramowaniem, efekt :hover na wierszach; HTML5, UTF-8, lang, semantyczne bloki; stopka z numerem zdającego.

Ocenie podlegać będzie

  • poprawność kwerend i zrzutów,
  • logowanie i ochrona strony,
  • LEFT JOIN i warunkowy odnośnik,
  • lista zaległych i formularz,
  • struktura, CSS i porządek w plikach.
Wskazówki
  • Zaległe: SELECT tytul FROM wypozyczenia JOIN ksiazki USING(idKsiazka) LEFT JOIN zwroty USING(idWypozyczenie) WHERE zwroty.idZwrot IS NULL.
  • Liczba wierszy: mysqli_num_rows($wynik).
  • Lekcje 11, 12, 17 i 18.