Odczyt danych — SELECT przez PDO

🎯 Po tej lekcji będziesz pobierać dane z bazy przez query(), fetch() i fetchAll() oraz bezpiecznie wyświetlać je w HTML przez htmlspecialchars.

Masz już połączenie z bazą i obiekt $pdo. Czas wykonać pierwsze zapytania SELECT i wyświetlić dane na stronie.

query() — zapytania bez danych od użytkownika

Najprostszą metodą jest query(). Przyjmuje gotowy łańcuch SQL i zwraca obiekt PDOStatement z wynikami:

<?php
declare(strict_types=1);

require 'db.php'; // $pdo gotowe

$stmt = $pdo->query('SELECT id, tytul, ukonczone FROM zadania ORDER BY id DESC');
⚠️ query() tylko dla stałych zapytań: Użyj query() wyłącznie wtedy, gdy zapytanie zawiera stałe wartości zapisane w kodzie — żadnych danych od użytkownika. Gdy potrzebujesz filtrować po $_GET['id'] czy $_POST['szukaj'], zawsze używaj prepare() i execute(). Szczegóły w następnej lekcji.

fetchAll() — wszystkie wiersze naraz

fetchAll() pobiera wszystkie wiersze z wyniku do jednej tablicy PHP:

<?php
declare(strict_types=1);

require 'db.php';

$stmt    = $pdo->query('SELECT id, tytul, ukonczone FROM zadania ORDER BY id DESC');
$zadania = $stmt->fetchAll(); // tablica tablic asocjacyjnych

foreach ($zadania as $z) {
    echo $z['id'] . ': ' . $z['tytul'] . "\n"; // kontekst CLI; w HTML użyj htmlspecialchars()
}

Dzięki atrybutowi PDO::FETCH_ASSOC ustawionemu przy połączeniu każdy wiersz to tablica asocjacyjna: $z['id'], $z['tytul'], $z['ukonczone'].

Gdy tabela jest pusta

fetchAll() zwraca pustą tablicę [] — nie false ani null. Możesz bezpiecznie sprawdzić if ($zadania === []).

fetch() — jeden wiersz naraz

Gdy potrzebujesz tylko jednego wiersza (np. szczegóły konkretnego zadania), użyj fetch(). Zwraca jeden wiersz jako tablicę lub false gdy wyników nie ma:

<?php
declare(strict_types=1);

require 'db.php';

// Stałe zapytanie — query() jest tu bezpieczne
$stmt    = $pdo->query('SELECT * FROM zadania WHERE id = 1');
$zadanie = $stmt->fetch();

if ($zadanie === false) {
    echo 'Nie znaleziono zadania.';
} else {
    echo htmlspecialchars($zadanie['tytul'], ENT_QUOTES, 'UTF-8') . ': ' . htmlspecialchars($zadanie['opis'], ENT_QUOTES, 'UTF-8');
}

Możesz też używać fetch() w pętli, by przetwarzać wiersze jeden po drugim — przydatne przy bardzo dużych zbiorach danych, gdy nie chcesz ładować wszystkiego do pamięci naraz:

<?php
declare(strict_types=1);

require 'db.php';

$stmt = $pdo->query('SELECT id, tytul FROM zadania');

while ($z = $stmt->fetch()) {
    echo $z['id'] . ': ' . $z['tytul'] . "\n";
}

Bezpieczne wyświetlanie danych w HTML

Dane z bazy to niezaufane dane — ktoś mógł wpisać <script>alert('XSS')</script> w pole tytułu. Zawsze owijaj wartości przez htmlspecialchars() przed wstawieniem do HTML:

<?php
declare(strict_types=1);

require 'db.php';

$stmt    = $pdo->query('SELECT id, tytul, opis FROM zadania ORDER BY id DESC');
$zadania = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="pl">
<head><meta charset="UTF-8"><title>Lista zadań</title></head>
<body>

<h1>Moje zadania</h1>

<?php if ($zadania === []): ?>
    <p>Brak zadań — dodaj pierwsze!</p>
<?php else: ?>
    <ul>
    <?php foreach ($zadania as $z): ?>
        <li>
            <strong><?= htmlspecialchars($z['tytul'], ENT_QUOTES, 'UTF-8') ?></strong>
            — <?= htmlspecialchars($z['opis'], ENT_QUOTES, 'UTF-8') ?>
        </li>
    <?php endforeach; ?>
    </ul>
<?php endif; ?>

</body>
</html>
💡 htmlspecialchars() przy każdym echo z bazy: Zapamiętaj zasadę: dane wychodzące z bazy do HTML zawsze przez htmlspecialchars(ENT_QUOTES, 'UTF-8'). Tak samo jak dane z formularzy. Baza nie oczyszcza danych — to zadanie warstwy wyświetlania.

Zliczanie wierszy

Gdy chcesz wiedzieć, ile rekordów pasuje do zapytania, możesz użyć COUNT(*) w SQL:

<?php
declare(strict_types=1);

require 'db.php';

$stmt  = $pdo->query('SELECT COUNT(*) AS ile FROM zadania WHERE ukonczone = 0');
$wiersz = $stmt->fetch();

echo 'Nieukończonych zadań: ' . $wiersz['ile'];

fetch() vs fetchAll() — kiedy co?

| Metoda | Zwraca | Kiedy używać | |---|---|---| | fetchAll() | Tablicę wszystkich wierszy | Lista na stronie, dropdown, eksport | | fetch() | Jeden wiersz lub false | Szczegóły jednego rekordu | | fetch() w pętli while | Wiersz po wierszu | Bardzo duże zbiory (oszczędność pamięci) |

W następnej lekcji poznasz prepared statements — kluczowy mechanizm ochrony przed SQL injection, niezbędny gdy w zapytaniu pojawiają się dane od użytkownika.

Funkcje w tej lekcji

Sprawdź się

Jaka jest różnica między fetch() a fetchAll()?

Kiedy bezpiecznie użyć query() zamiast prepare()/execute()?

Dlaczego danych z bazy nie wstawiasz bezpośrednio do HTML przez echo $row['tytul']?

Ćwiczenie

Masz tabelę zadania (id, tytul, opis, ukonczone, created_at). Napisz kod PHP, który pobiera z bazy wszystkie NIEUKOŃCZONE zadania (ukonczone = 0), posortowane po id malejąco, i wyświetla je jako listę HTML <ul><li> z tytułem każdego zadania. Bezpiecznie obsłuż wyjście. Jeśli brak zadań — wyświetl 'Brak zadań do zrobienia.'

Pokaż rozwiązanie
<?php
declare(strict_types=1);

// Zakładamy, że $pdo jest dostępne z pliku konfiguracyjnego bazy
require 'db.php';

$stmt = $pdo->query(
    'SELECT id, tytul FROM zadania WHERE ukonczone = 0 ORDER BY id DESC'
);
$zadania = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="pl">
<head><meta charset="UTF-8"><title>Zadania</title></head>
<body>
<?php if ($zadania === []): ?>
    <p>Brak zadań do zrobienia.</p>
<?php else: ?>
    <ul>
    <?php foreach ($zadania as $z): ?>
        <li><?= htmlspecialchars($z['tytul'], ENT_QUOTES, 'UTF-8') ?></li>
    <?php endforeach; ?>
    </ul>
<?php endif; ?>
</body>
</html>