Projekt: Księga gości

🎯 Po tej lekcji zbudujesz od zera działającą księgę gości, która waliduje wpisy, przechowuje je w sesji i bezpiecznie wyświetla przez htmlspecialchars.

Czas połączyć wszystko, czego nauczyłeś się w tym module: formularze, odczyt $_POST, walidację, sanityzację, sesje i bezpieczne wyświetlanie danych. Zbudujesz księgę gości — klasyczny projekt webowy, który działa jak miniaturowy serwis społecznościowy.

💡 Sesja zamiast bazy danych: W tej lekcji wpisy są przechowywane w $_SESSION — żyją tylko przez czas trwania sesji i nie są widoczne dla innych użytkowników. Trwałe przechowywanie z dostępem dla wszystkich (baza danych) pojawi się w Module 8. Na razie skupiamy się na technikach webowych.

Co będziemy budować

Aplikacja składa się z dwóch części na tej samej stronie:

  1. Formularz — imię (wymagane), e-mail (opcjonalny, ale jeśli podany — musi być poprawny), treść wpisu (wymagana, minimum 10 znaków)
  2. Lista wpisów — wszystkie wpisy z sesji, wyświetlone od najnowszego, bezpiecznie przez htmlspecialchars

Krok 1 — inicjalizacja i odczyt sesji

Każdy plik PHP z sesjami zaczynamy tak samo — session_start() na samym początku:

<?php
declare(strict_types=1);

session_start();

// Upewnij się, że tablica wpisów istnieje w sesji
if (!isset($_SESSION['wpisy'])) {
    $_SESSION['wpisy'] = [];
}

Krok 2 — obsługa wysłanego formularza

Przed wyświetleniem czegokolwiek przetwarzamy dane, jeśli formularz był wysłany:

$imie    = '';
$email   = '';
$tresc   = '';
$bledy   = [];
$sukces  = false;

if (isset($_POST['wyslij'])) {
    // Sanityzacja
    $imie  = trim($_POST['imie']  ?? '');
    $email = trim($_POST['email'] ?? '');
    $tresc = trim($_POST['tresc'] ?? '');

    // Walidacja
    if ($imie === '') {
        $bledy[] = 'Imię jest wymagane.';
    }

    if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
        $bledy[] = 'Podany adres e-mail jest nieprawidłowy.';
    }

    if ($tresc === '') {
        $bledy[] = 'Treść wpisu jest wymagana.';
    } elseif (strlen($tresc) < 10) {
        $bledy[] = 'Wpis musi mieć co najmniej 10 znaków.';
    }

    if ($bledy === []) {
        // Dodaj nowy wpis na POCZĄTKU tablicy (od najnowszego)
        array_unshift($_SESSION['wpisy'], [
            'imie'  => $imie,
            'email' => $email,
            'tresc' => $tresc,
            'data'  => date('d.m.Y H:i'),
        ]);
        $sukces = true;
        // Wyczyść pola po sukcesie
        $imie = $email = $tresc = '';
    }
}

Krok 3 — szablon HTML

Teraz wyświetlamy wszystko — błędy, formularz i listę wpisów:

?>
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Księga gości</title>
    <style>
        body       { font-family: sans-serif; max-width: 700px; margin: 2rem auto; padding: 0 1rem; }
        .blad      { color: #c00; }
        .sukces    { color: #080; }
        .wpis      { border: 1px solid #ddd; border-radius: 6px; padding: 1rem; margin: 1rem 0; }
        .wpis-meta { color: #666; font-size: .85em; }
    </style>
</head>
<body>

<h1>Księga gości</h1>

<?php if ($sukces): ?>
    <p class="sukces">Dziękujemy za wpis!</p>
<?php endif; ?>

<?php if ($bledy !== []): ?>
    <ul class="blad">
    <?php foreach ($bledy as $blad): ?>
        <li><?= htmlspecialchars($blad, ENT_QUOTES, 'UTF-8') ?></li>
    <?php endforeach; ?>
    </ul>
<?php endif; ?>

<h2>Zostaw wpis</h2>
<form method="post" action="">
    <p>
        <label>Imię <em>(wymagane)</em>:<br>
        <input type="text" name="imie" value="<?= htmlspecialchars($imie, ENT_QUOTES, 'UTF-8') ?>" size="40"></label>
    </p>
    <p>
        <label>E-mail <em>(opcjonalny)</em>:<br>
        <input type="email" name="email" value="<?= htmlspecialchars($email, ENT_QUOTES, 'UTF-8') ?>" size="40"></label>
    </p>
    <p>
        <label>Wpis <em>(min. 10 znaków)</em>:<br>
        <textarea name="tresc" rows="4" cols="50"><?= htmlspecialchars($tresc, ENT_QUOTES, 'UTF-8') ?></textarea></label>
    </p>
    <button type="submit" name="wyslij">Dodaj wpis</button>
</form>

<h2>Wpisy (<?= count($_SESSION['wpisy']) ?>)</h2>

<?php if ($_SESSION['wpisy'] === []): ?>
    <p>Brak wpisów. Bądź pierwszy!</p>
<?php else: ?>
    <?php foreach ($_SESSION['wpisy'] as $wpis): ?>
    <div class="wpis">
        <p><?= htmlspecialchars($wpis['tresc'], ENT_QUOTES, 'UTF-8') ?></p>
        <p class="wpis-meta">
            — <?= htmlspecialchars($wpis['imie'], ENT_QUOTES, 'UTF-8') ?>
            <?php if ($wpis['email'] !== ''): ?>
                (<a href="mailto:<?= htmlspecialchars($wpis['email'], ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($wpis['email'], ENT_QUOTES, 'UTF-8') ?></a>)
            <?php endif; ?>
            | <?= htmlspecialchars($wpis['data'], ENT_QUOTES, 'UTF-8') ?>
        </p>
    </div>
    <?php endforeach; ?>
<?php endif; ?>

</body>
</html>

Kompletny plik — wszystko razem

Oto gotowy plik ksiega-gosci.php łączący wszystkie kroki:

<?php
declare(strict_types=1);

session_start();

if (!isset($_SESSION['wpisy'])) {
    $_SESSION['wpisy'] = [];
}

$imie   = '';
$email  = '';
$tresc  = '';
$bledy  = [];
$sukces = false;

if (isset($_POST['wyslij'])) {
    $imie  = trim($_POST['imie']  ?? '');
    $email = trim($_POST['email'] ?? '');
    $tresc = trim($_POST['tresc'] ?? '');

    if ($imie === '') {
        $bledy[] = 'Imię jest wymagane.';
    }
    if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
        $bledy[] = 'Podany adres e-mail jest nieprawidłowy.';
    }
    if ($tresc === '') {
        $bledy[] = 'Treść wpisu jest wymagana.';
    } elseif (strlen($tresc) < 10) {
        $bledy[] = 'Wpis musi mieć co najmniej 10 znaków.';
    }

    if ($bledy === []) {
        array_unshift($_SESSION['wpisy'], [
            'imie'  => $imie,
            'email' => $email,
            'tresc' => $tresc,
            'data'  => date('d.m.Y H:i'),
        ]);
        $sukces = true;
        $imie = $email = $tresc = '';
    }
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Księga gości</title>
    <style>
        body       { font-family: sans-serif; max-width: 700px; margin: 2rem auto; padding: 0 1rem; }
        .blad      { color: #c00; }
        .sukces    { color: #080; }
        .wpis      { border: 1px solid #ddd; border-radius: 6px; padding: 1rem; margin: 1rem 0; }
        .wpis-meta { color: #666; font-size: .85em; }
    </style>
</head>
<body>

<h1>Księga gości</h1>

<?php if ($sukces): ?>
    <p class="sukces">Dziękujemy za wpis!</p>
<?php endif; ?>

<?php if ($bledy !== []): ?>
    <ul class="blad">
    <?php foreach ($bledy as $blad): ?>
        <li><?= htmlspecialchars($blad, ENT_QUOTES, 'UTF-8') ?></li>
    <?php endforeach; ?>
    </ul>
<?php endif; ?>

<h2>Zostaw wpis</h2>
<form method="post" action="">
    <p>
        <label>Imię <em>(wymagane)</em>:<br>
        <input type="text" name="imie"
               value="<?= htmlspecialchars($imie, ENT_QUOTES, 'UTF-8') ?>" size="40"></label>
    </p>
    <p>
        <label>E-mail <em>(opcjonalny)</em>:<br>
        <input type="email" name="email"
               value="<?= htmlspecialchars($email, ENT_QUOTES, 'UTF-8') ?>" size="40"></label>
    </p>
    <p>
        <label>Wpis <em>(min. 10 znaków)</em>:<br>
        <textarea name="tresc" rows="4" cols="50"><?= htmlspecialchars($tresc, ENT_QUOTES, 'UTF-8') ?></textarea></label>
    </p>
    <button type="submit" name="wyslij">Dodaj wpis</button>
</form>

<h2>Wpisy (<?= count($_SESSION['wpisy']) ?>)</h2>

<?php if ($_SESSION['wpisy'] === []): ?>
    <p>Brak wpisów. Bądź pierwszy!</p>
<?php else: ?>
    <?php foreach ($_SESSION['wpisy'] as $wpis): ?>
    <div class="wpis">
        <p><?= htmlspecialchars($wpis['tresc'], ENT_QUOTES, 'UTF-8') ?></p>
        <p class="wpis-meta">
            — <?= htmlspecialchars($wpis['imie'], ENT_QUOTES, 'UTF-8') ?>
            <?php if ($wpis['email'] !== ''): ?>
                (<a href="mailto:<?= htmlspecialchars($wpis['email'], ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($wpis['email'], ENT_QUOTES, 'UTF-8') ?></a>)
            <?php endif; ?>
            | <?= htmlspecialchars($wpis['data'], ENT_QUOTES, 'UTF-8') ?>
        </p>
    </div>
    <?php endforeach; ?>
<?php endif; ?>

</body>
</html>

Co zastosowaliśmy?

Przejrzyj kod i znajdź te elementy — wszystkie poznałeś w tym module:

  • session_start() — inicjalizacja sesji na samym początku
  • isset($_POST['wyslij']) — sprawdzenie czy formularz wysłany
  • trim(...) — sanityzacja wejścia przed walidacją
  • filter_var(..., FILTER_VALIDATE_EMAIL) — walidacja adresu e-mail
  • htmlspecialchars(..., ENT_QUOTES, 'UTF-8') — przy każdym wyświetleniu danych z sesji
  • Składnia alternatywna foreach (...): ... endforeach; w szablonie
  • Wzorzec „formularz wysyła do samego siebie" (action="")

Rozbuduj sam

Masz działający fundament — oto kilka pomysłów na rozbudowę:

  1. Limit wpisów — zachowaj tylko ostatnie 20 wpisów: array_slice($_SESSION['wpisy'], 0, 20)
  2. Walidacja długości — ogranicz treść wpisu do 500 znaków przez strlen($tresc) > 500
  3. Usuwanie wpisów — dodaj formularz z przyciskiem usuń i indeksem wpisu
  4. Podgląd przed wysłaniem — pokaż podgląd wpisu i poproś o potwierdzenie
  5. Trwałe przechowywanie — w Module 8 nauczysz się zapisywać wpisy do bazy danych MySQL, żeby przetrwały sesję i były widoczne dla wszystkich
💡 Gratuluję! Właśnie napisałeś swoją pierwszą prawdziwą aplikację webową w PHP — z formularzem, walidacją, sesjami i ochroną przed XSS. To solidny fundament pod wszystko, co będzie dalej.

Funkcje w tej lekcji

Sprawdź się

Dlaczego w księdze gości używamy htmlspecialchars przy wyświetlaniu wpisów?

Gdzie ta aplikacja przechowuje wpisy do księgi gości?

Dlaczego trim() jest ważny przed zapisaniem wpisu do księgi gości?