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:
| Rezultat | Co jest oceniane | Jak zdobyć punkty |
|---|---|---|
| R.1 Operacje na bazie danych | import bazy do phpMyAdmin (zrzut import), plik kwerendy.txt, zrzuty kw1…kw5 z wynikami | poprawne zapytania SQL i zrzuty całego ekranu z paskiem zadań, nazwane dokładnie jak w poleceniu |
| R.2 Zawartość witryny | HTML5, lang="pl", UTF-8, tytuł strony, bloki semantyczne, nagłówki, tabele, formularze, obrazy z alt | dokładne teksty i znaczniki z arkusza |
| R.3 Działanie witryny | układ bloków zgodny z ilustracją, połączenie z styl.css, działające odnośniki i formularze | właściwy układ (flex, grid, float) i poprawne atrybuty |
| R.4 Styl CSS | kolory, marginesy, rozmiary, obramowania, efekt :hover | wartości podane w arkuszu, selektory zgodne z uwagami („wyłącznie selektorem tego znacznika”) |
| R.5 Skrypty | połączenie z bazą localhost/root/bez hasła, zapytania, generowanie HTML, zamknięcie połączenia | peł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 imysqli_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
whilepo wierszach wyniku i dokładnie takie elementy HTML, jakich wymaga arkusz. - „Zmodyfikowane zapytanie” – np. dodany
WHEREz 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 arkusza | Wzorzec skryptów | Lekcja |
|---|---|---|
| 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 INSERT | 7, 8, 9 |
| sklep z antykami (05) | trzy warianty tego samego skryptu (kategorie), przycisk „KUP” z value, INSERT, lista koszyka i suma | 10, 16 |
| portal laboratorium (10) | logowanie i sesja, ochrona strony, tabela z LEFT JOIN, link do pliku PDF, wylogowanie | 11, 12, 17 |
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.
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).
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.
Uzupełnij otwarcie i zamknięcie bloku PHP, aby skrypt wypisał tekst na stronie.
echo "Egzamin INF.03";
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ą.
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ędzie | Zastosowanie |
|---|---|
| XAMPP (panel Control Panel) | uruchamia serwer Apache (strony i PHP) oraz MySQL/MariaDB (baza); oba moduły muszą mieć status „Running” |
htdocs | katalog 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 kodu | Notepad++, VS Code, Brackets – pliki w kodowaniu UTF-8 |
| przeglądarka | testowanie; 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_cilub polski, np.utf8_polish_ci) → Utwórz. - Wybierz bazę → zakładka Import → plik
.sqlz 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.txti 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.
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.
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.
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.
Uzupełnij połączenie z bazą w XAMPP: serwer lokalny, użytkownik administracyjny bez hasła i baza o nazwie podroze.
$polaczenie = mysqli_connect("", "", "", "");
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.
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).
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 arkusza | Wzorzec | Przykład na bazie podroze |
|---|---|---|
| wybór kilku pól z warunkiem | SELECT pola FROM tabela WHERE warunek; | SELECT idAtrakcja, plik, nazwa FROM atrakcje WHERE kraj = 'Polska'; |
| warunek na identyfikatorze | WHERE id = liczba | SELECT nazwa, cena FROM atrakcje WHERE idKategoria = 2; |
| dwa warunki | WHERE a AND b | SELECT nazwa FROM atrakcje WHERE idRegion = 1 AND idKategoria = 1; |
| sortowanie | ORDER 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 aliasem | COUNT(*) AS alias ... GROUP BY | SELECT regiony.nazwa, COUNT(*) AS 'Liczba atrakcji' FROM atrakcje JOIN regiony USING(idRegion) GROUP BY regiony.nazwa; |
| wstawienie rekordu | INSERT INTO t (kolumny) VALUES (...) | INSERT INTO wizyty (idAtrakcja, idTurysta, dataWizyty) VALUES (5, 2, '2026-06-01'); |
| data bez godziny, liczenie po dniach | DATE(pole), GROUP BY DATE(pole) | SELECT dataWizyty, COUNT(*) FROM wizyty GROUP BY dataWizyty; |
| relacja opcjonalna (LEFT JOIN) | LEFT JOIN zwraca wszystkie wiersze lewej tabeli | SELECT atrakcje.nazwa, wizyty.dataWizyty FROM atrakcje LEFT JOIN wizyty USING(idAtrakcja); |
Ćwiczenia (wynik sprawdź w phpMyAdmin)
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).
-- 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;
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.
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').
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).
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.
Wybierz nazwę i cenę atrakcji z Polski, od najdroższej do najtańszej.
SELECT nazwa, cena FROM
WHERE kraj =
cena ;
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;
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;
Zapytanie ma dwa błędy: operator porównania z innego języka i niepełne sortowanie. Popraw je.
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
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(zestawutf8_unicode_ci), zaimportujpodroze.sqlz paczki podroze.zip, wykonaj zrzutimport.png(cały ekran). - Zapytanie 1: pola
nazwa,kraj,cenaatrakcji 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, data2026-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 jakokw1.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.
<!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>
| Zapis | Wynik |
|---|---|
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;
?>
Zasada Przykład / uwaga
początek: <<< i dowolna etykieta, potem nowa linia echo <<<SKRYPT1 – nazwa jest dowolna (HTML, KOD, SKRYPT1), wielkość liter ma znaczenie
koniec: ta sama etykieta w osobnej linii i średnik SKRYPT1; – od PHP 7.3 etykieta może być wcięta, ale w starszych wersjach musi stać w pierwszej kolumnie
cudzysłowy w treści nie 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 funkcji wprost 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';
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.
<?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;
?>
| Zasada | Przykład / uwaga |
|---|---|
początek: <<< i dowolna etykieta, potem nowa linia | echo <<<SKRYPT1 – nazwa jest dowolna (HTML, KOD, SKRYPT1), wielkość liter ma znaczenie |
| koniec: ta sama etykieta w osobnej linii i średnik | SKRYPT1; – od PHP 7.3 etykieta może być wcięta, ale w starszych wersjach musi stać w pierwszej kolumnie |
| cudzysłowy w treści | nie 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 funkcji | wprost 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 |
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”.
(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.
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.
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).
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.
Jak zakończyć blok heredoc rozpoczęty od echo << Linią zawierającą wyłącznie tę samą etykietę i średnik: Pokaż przykładową odpowiedź
SKRYPT1; (wielkość liter musi się zgadzać).
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.
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.";
W skrypcie brakuje znaków $ przy zmiennej i średnika. Popraw kod tak, aby wypisał „Witaj, Anna”.
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">
Blok heredoc nie jest poprawnie zamknięty: etykieta końcowa ma inną wielkość liter niż początkowa i brakuje średnika. Popraw 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.
<?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";
}
?>
| Konstrukcja | Uwagi |
|---|---|
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 ?? $b | wartość $a, a gdy jej nie ma – $b |
<?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"]);
}
?>
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.
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.
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";
}
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";
}
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.
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";
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.
<?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ętla | Kiedy |
|---|---|
for | znana liczba powtórzeń lub potrzebny licznik |
while | dopóki warunek jest prawdziwy – np. dopóki zapytanie zwraca kolejny wiersz |
foreach | przejście po wszystkich elementach tablicy (także asocjacyjnej) |
break, continue | przerwanie pętli, przejście do następnego obrotu |
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.
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.
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.
Wypisz kroki od 1 do 5 pętlą for.
<?php
($i = 1; $i 5; $i) {
echo "Krok $i<br>";
}
Pętla while odlicza od 5 do 1, ale nigdy się nie kończy. Popraw 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>";
Suma cen jest zawsze równa ostatniej cenie, bo zmienna jest zerowana w każdym obrocie pętli. Popraw 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.
<?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>";
}
?>
| Funkcja | Opis |
|---|---|
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 |
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].
Jak dodać element na koniec tablicy $koszyk?
Pokaż przykładową odpowiedź
$koszyk[] = $element;
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.
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";
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>";
}
Elementy tablicy asocjacyjnej odczytujemy po kluczu w cudzysłowach. Popraw odczyt nazwy i ceny produktu.
Pętla przechodzi po pozycjach koszyka, ale odczytuje dane z całej tablicy zamiast z bieżącej pozycji. Popraw 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.
<?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++;
}
?>
| Funkcja | Opis |
|---|---|
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ę |
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).
Jak w PHP wartość z adresu URL zamienić na liczbę całkowitą?
Pokaż przykładową odpowiedź
intval($_GET["id"]) lub rzutowanie (int)$_GET["id"].
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).
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.
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, ",", " ");
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)) {
();
}
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).
Funkcja ma zwrócić kwotę podatku, ale po jej wywołaniu nic nie jest wypisywane. Popraw 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.
| Funkcja | Dział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 |
<?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
$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_connectpodasz nazwę bazy,mysqli_select_dbjest 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żytkownikroot, serwerlocalhost. - Połączenie otwierasz raz na początku strony i zamykasz na końcu – nie otwieraj nowego przy każdym zapytaniu.
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.
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)).
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.
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);
W skrypcie brakuje argumentów: puste hasło przy łączeniu, połączenie w mysqli_query i w mysqli_close. Popraw wywołania.
Sprawdź, czy zapytanie się powiodło; jeśli nie, wypisz komunikat błędu serwera.
$wynik = mysqli_query($polaczenie, $sql);
if () {
echo "Błąd: " . ($polaczenie);
}
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.
<?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>";
?>
<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>
<?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>";
}
?>
<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 arkusza Realizacja
„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>
}
<?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>";
?>
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 arkusza | Realizacja |
|---|---|
| „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']}' |
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.
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).
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.
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 "";
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 "</>";
Pętla wypisuje w nieskończoność pierwszy wiersz, bo wiersz jest pobierany tylko raz przed pętlą. Popraw pętlę.
Wypisanie obrazu powoduje błąd składni, bo elementy tablicy w napisie z apostrofami są zapisane bez klamer. Popraw instrukcję echo.
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.
<?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);
?>
<?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 arkusza | Realizacja |
|---|---|
| „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']}' |
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.
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).
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.
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>";
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 = ;");
Wartość jest przekazywana w adresie strony (metoda GET), ale skrypt odczytuje ją z tablicy metody POST. Popraw kod.
Wartość tekstowa w zapytaniu SQL musi być w apostrofach. Popraw zapytanie, które ma wybrać atrakcje z podanego kraju.
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)
<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);
}
?>
| Element | Znaczenie |
|---|---|
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 pola | to 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 przycisku | przekazuje wartość (np. id produktu przy przycisku „KUP”) |
CURDATE(), NOW() | bieżąca data / data i godzina w SQL |
Przekierowanie po zapisie
<?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
}
?>
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.
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.
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.
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>
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);
}
Zapytanie INSERT zawiera datę wstawioną bez apostrofów, więc zostanie zinterpretowana jako działanie matematyczne. Popraw zapytanie.
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.
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.
<?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).
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/”.
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.
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.
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>";
}
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"];
Wartość do zapłaty jest zawsze równa cenie ostatniego produktu, bo kolejne ceny nie są dodawane. Popraw kod.
Dodanie do koszyka nie działa, bo skrypt nie uruchomił sesji. Popraw 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.
| Funkcja | Opis (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 |
<?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>
<?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>
<?php
session_start();
if (isset($_SESSION["uzytkownik"])) {
unset($_SESSION["uzytkownik"]); // usunięcie zmiennej
session_destroy(); // koniec sesji
}
header("Location: login.php");
exit();
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).
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.
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).
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.
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;
}
Wyloguj użytkownika: uruchom sesję, zniszcz ją i wróć na stronę główną.
();
();
header("Location: index.php");
exit;
Strona chroniona nie działa, bo brakuje uruchomienia sesji i zatrzymania skryptu po przekierowaniu. 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.
-- 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;
<?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>";
?>
| Potrzeba | Rozwiązanie |
|---|---|
| wiersze bez odpowiednika w drugiej tabeli | LEFT JOIN (wszystkie wiersze lewej tabeli) |
| pusta wartość z bazy | NULL w PHP to null; sprawdzaj !empty($pole) lub $pole !== null |
| odnośnik w nowej karcie | target='_blank' |
| data dla każdego dnia w grupie | SELECT DATE(dataWypozyczenia), COUNT(*) FROM wypozyczenia GROUP BY DATE(dataWypozyczenia) |
| data bieżąca w SQL i PHP | CURDATE() / date("Y-m-d") |
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.
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ń.
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.
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;
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>";
}
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.
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
Zadanie
Na bazie podroze (podroze.zip) wykonaj witrynę „Atlas atrakcji” zgodnie z wymaganiami.
Założenia
- Struktura:
header(nagłówek h1 „Atlas atrakcji”),mainzasideisection,article,footerz numerem zdającego; HTML5,lang="pl",UTF-8, zewnętrznystyl.css(układ Grid lub Flex). - Skrypt 1 (w
aside): odnośniki z nazwami regionów; każdy prowadzi doindex.phpz parametrem GETregion. - 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
wizytyrekord z datą bieżącą (CURDATE()), a następnie przekierowuje doindex.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
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
zakupydla 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
$_GETalbo$_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
<?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
<?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żenie | Opis | Zabezpieczenie w PHP |
|---|---|---|
| SQL Injection | dane użytkownika zmieniają strukturę zapytania | zapytania 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ów | htmlspecialchars($tekst) przy wypisywaniu danych od użytkownika |
| CSRF | fałszywe żądanie wykonane w imieniu zalogowanego użytkownika | token CSRF w formularzach (bin2hex(random_bytes(16)) w sesji), SameSite |
| jawne hasła | hasło w bazie lub kodzie można odczytać po wycieku | password_hash(), password_verify() |
| przejęcie sesji | kradzież identyfikatora sesji | HTTPS, session_regenerate_id(true) po zalogowaniu, flagi HttpOnly/Secure |
| niebezpieczne pliki | wysłanie skryptu zamiast obrazu | sprawdzenie typu mime_content_type, własna nazwa pliku, katalog bez wykonywania PHP |
| wyciek informacji | komunikaty błędów pokazują ścieżki i zapytania | w produkcji display_errors = Off, błędy do logu |
// 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]]);
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.
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.
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.
Identyfikator z adresu jest wklejany do zapytania bez żadnej kontroli, co umożliwia SQL Injection. Zabezpiecz go rzutowaniem na liczbę całkowitą.
Wypisz komentarz od użytkownika tak, aby znaczniki HTML nie zostały wykonane (ochrona przed XSS).
echo "<p>Komentarz: " . ($_POST["komentarz"]) . "</p>";
Zapisz hasło jako skrót przy rejestracji i sprawdź je przy logowaniu.
$hash = ($haslo, PASSWORD_DEFAULT);
if (($wpisane, $hash)) {
echo "Poprawne hasło";
}
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 / objaw | Przyczyna | Rozwiązanie |
|---|---|---|
Parse error: syntax error, unexpected ... | brak średnika, nawiasu lub cudzysłowu; zły cudzysłów wewnątrz echo | sprawdź linię z komunikatu i poprzednią; użyj apostrofów w atrybutach HTML |
Undefined variable $x | literówka w nazwie zmiennej lub zmienna poza zakresem funkcji | sprawdź 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 $_GET | strona otwarta bez parametru | użyj isset($_GET['id']) lub ?? |
mysqli_connect(): (HY000/1049): Unknown database | zła nazwa bazy lub baza nie została utworzona | sprawdź nazwę w phpMyAdmin i literówki |
Access denied for user 'root'@'localhost' | złe hasło lub użytkownik | na egzaminie: użytkownik root, hasło puste |
mysqli_fetch_array() expects parameter 1 to be mysqli_result, bool given | zapytanie zwróciło false (błąd SQL) | wypisz mysqli_error($polaczenie) i popraw zapytanie |
Cannot modify header information – headers already sent | wyjś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ów | włącz display_errors lub zajrzyj do error.log |
| „krzaczki” zamiast polskich liter | złe kodowanie pliku lub połączenia | plik UTF-8, <meta charset="UTF-8">, mysqli_set_charset($p, "utf8"), baza w utf8_unicode_ci |
| tabela pusta mimo zapytania | warunek nie pasuje do danych lub zły parametr GET | wykonaj to samo zapytanie w phpMyAdmin i porównaj |
| obrazy się nie ładują | zła ścieżka lub wielkość liter w nazwie pliku | sprawdź kod źródłowy strony (Ctrl+U) i kartę Network |
<?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.
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.
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.
Skrypt zgłasza błąd Parse error: w pierwszej linii brakuje średnika. Popraw kod.
Napis z zapytaniem SQL nie jest zamknięty cudzysłowem, a wywołanie funkcji nawiasem. Popraw instrukcję.
W pętli użyto nazwy zmiennej z literówką i nieistniejącego klucza tablicy (nazwy kolumn zapisujemy tak jak w bazie). Popraw 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.phpiatrakcja.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
PolskiiHiszpaniijako obrazy-odnośniki doatrakcja.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).
<?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>
<?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>
* { 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%; }
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:
titledaje dymek,alt– tekst alternatywny; klasaminiaturamatransition: opacity 1si zmianęopacityprzy:hover. - Zapytanie 4 po
JOIN ... USING(idKategoria)ma kolumnęnazwaw obu tabelach – dlatego użytoatrakcje.nazwai aliasukategoriadla drugiej. - Jeżeli parametr
idjest niepoprawny,mysqli_fetch_arrayzwrócinull– warunekif ($wiersz)zapobiega błędom.
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.
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.
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>";
}
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>";
Wyniki zapytania mają kolumny „nazwa” i alias „kategoria”, a skrypt odwołuje się do nazw z prefiksem tabeli. Popraw odczyt pól.
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).
<?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>
* { 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ąpostdo tego samego pliku. float: leftna obrazach itext-align: centerw*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.
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.
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.
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>";
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>";
}
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).
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*).
<?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>
<?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>
<?php
session_start();
if (isset($_SESSION["uzytkownik"])) {
unset($_SESSION["uzytkownik"]);
session_destroy();
}
header("Location: login.php");
exit();
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 arkusza | Gdzie 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 |
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.
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.
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.
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;
}
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;
Skrypt wylogowania nie kończy sesji: brakuje jej uruchomienia przed zniszczeniem i przekierowania. 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
| Rezultat | Sprawdź |
|---|---|
| R.1 baza | nazwa 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łanie | ukł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 CSS | każ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 skrypty | połą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 wprzegladarka.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
htdocsdo folderu zdającego (po ostatniej zmianie!).
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.
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.
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.
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.
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>
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
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 wkwerendy.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.phpiwyloguj.phpzgodnie ze wzorcem z lekcji 20 (logowanieadmin/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 JOINi warunkiemzwroty.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:hoverna 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.