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');
$_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>
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()?
fetch() pobiera kolejny wiersz z wyniku i zwraca tablicę (lub false gdy wierszy już nie ma). fetchAll() od razu pobiera wszystkie wiersze do tablicy PHP. fetch() jest lepszy gdy przetwarzasz wiersz po wierszu, fetchAll() gdy potrzebujesz całej listy naraz.
Kiedy bezpiecznie użyć query() zamiast prepare()/execute()?
query() jest bezpieczne tylko dla zapytań z wyłącznie stałymi wartościami zaszytymi w kodzie (np. SELECT * FROM zadania). Gdy zapytanie zależy od danych wejściowych użytkownika, zawsze używaj prepare() i execute() — to omówimy w następnej lekcji.
Dlaczego danych z bazy nie wstawiasz bezpośrednio do HTML przez echo $row['tytul']?
Dane w bazie mogą zawierać < > & " — znaki specjalne HTML. Ktoś mógł je wpisać w formularzu celowo (atak XSS) albo przez nieuwagę. htmlspecialchars() zamienia je na bezpieczne encje HTML przed wyświetleniem.
Ć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>