Połączenie z bazą przez PDO
🎯 Po tej lekcji będziesz łączyć PHP z MySQL przez PDO, ustawiać tryb błędów i kodowanie utf8mb4 oraz przechwytywać wyjątki PDOException.
W poprzedniej lekcji pisałeś zapytania SQL bezpośrednio w phpMyAdmin. Teraz połączysz PHP z bazą danych przez PDO — PHP Data Objects — i zaczniesz korzystać z niej w kodzie.
Dlaczego PDO, a nie mysql_* ani mysqli?
PHP historycznie miało kilka sposobów łączenia się z MySQL:
mysql_*— stare funkcje (np.mysql_connect,mysql_query) usunięte z PHP 7. Nie używaj ich. Jeśli widzisz je w tutorialach, ten tutorial jest przestarzały.mysqli— następcamysql_*, dostępny od PHP 5. Działa tylko z MySQL/MariaDB.- PDO — nowoczesne, obiektowe rozszerzenie PHP obsługujące wiele różnych baz danych (MySQL, PostgreSQL, SQLite i inne). Jeden interfejs, wiele baz. To jest właściwy wybór.
DSN — łańcuch połączenia
PDO identyfikuje bazę danych przez DSN (Data Source Name) — łańcuch tekstowy w określonym formacie:
mysql:host=localhost;dbname=moja_aplikacja;charset=utf8mb4
Poszczególne elementy:
| Element | Znaczenie |
|---|---|
| mysql: | Sterownik — używamy MySQL/MariaDB |
| host=localhost | Serwer bazy danych (lub adres IP) |
| dbname=moja_aplikacja | Nazwa bazy danych |
| charset=utf8mb4 | Kodowanie znaków (zawsze utf8mb4) |
Dlaczego charset=utf8mb4, nie utf8?
MySQL ma dwa kodowania o mylących nazwach:
utf8— historyczna 3-bajtowa implementacja. Nie obsługuje emoji (🎉) ani niektórych rzadkich znaków.utf8mb4— prawdziwe UTF-8, 4 bajty. Obsługuje cały standard Unicode.
Zawsze używaj utf8mb4. Ustawiasz je w DSN (charset=utf8mb4), a MySQL zadba o resztę: prawidłowe porównywanie i sortowanie polskich znaków.
Tworzenie połączenia
Połączenie otwierasz konstruktorem klasy PDO:
<?php
declare(strict_types=1);
$dsn = 'mysql:host=localhost;dbname=moja_aplikacja;charset=utf8mb4';
$pdo = new PDO($dsn, 'uzytkownik', 'haslo');
Trzy argumenty: DSN, nazwa użytkownika, hasło.
Niezbędna konfiguracja — atrybuty
Domyślna konfiguracja PDO jest zbyt łagodna na błędy. Zawsze ustawiaj dwa atrybuty:
<?php
declare(strict_types=1);
$dsn = 'mysql:host=localhost;dbname=moja_aplikacja;charset=utf8mb4';
$pdo = new PDO($dsn, 'uzytkownik', 'haslo', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
Co te atrybuty robią?
PDO::ERRMODE_EXCEPTION— każdy błąd SQL (literówka w nazwie tabeli, brak kolumny, problem z siecią) rzuca wyjątekPDOException. Bez tego PDO cicho zwracafalsei łatwo przeoczyć błąd.PDO::FETCH_ASSOC— wiersze zwracane przezfetch()ifetchAll()będą tablicami asocjacyjnymi ($row['tytul']), nie numerycznymi ($row[0]). Czytelniejszy kod.
Obsługa błędów — try/catch z PDOException
Jeżeli połączenie się nie powiedzie (zła nazwa bazy, złe hasło, serwer nie odpowiada), PDO rzuci PDOException. Chwyć go przez try/catch:
<?php
declare(strict_types=1);
$dsn = 'mysql:host=localhost;dbname=moja_aplikacja;charset=utf8mb4';
try {
$pdo = new PDO($dsn, 'uzytkownik', 'haslo', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (PDOException $e) {
// Zaloguj szczegóły błędu po STRONIE SERWERA — nie wyświetlaj ich użytkownikowi
error_log('Błąd połączenia PDO: ' . $e->getMessage());
// Użytkownikowi pokaż ogólny komunikat
echo 'Przepraszamy, wystąpił problem techniczny. Spróbuj ponownie za chwilę.';
exit();
}
// Tutaj $pdo jest gotowe do użycia
echo 'Połączono z bazą danych.';
$e->getMessage() często zawiera nazwę bazy, użytkownika, tabelę lub fragment hasła. To dane wrażliwe dla atakującego. Loguj je przez error_log() lub dedykowany logger (Monolog z Modułu 7), a użytkownikowi pokazuj tylko ogólny komunikat.Kompletny przykład — plik konfiguracyjny bazy
W prawdziwej aplikacji tworzymy jeden centralny plik z konfiguracją połączenia. Reszta kodu go dołącza:
<?php
declare(strict_types=1);
// db.php — wczytyj przez require 'db.php';
$dsn = 'mysql:host=localhost;dbname=moja_aplikacja;charset=utf8mb4';
try {
$pdo = new PDO($dsn, 'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (PDOException $e) {
error_log('PDO: ' . $e->getMessage());
http_response_code(503);
exit('Błąd serwera.');
}
W kolejnych lekcjach przyjmiemy, że $pdo jest już dostępne — właśnie z takiego pliku konfiguracyjnego.
.env — z pakietem vlucas/phpdotenv (Packagist). Na razie użyjemy prostych zmiennych, ale warto to wiedzieć.W kolejnej lekcji wykonasz pierwsze zapytania SELECT przez PDO i zobaczysz, jak iterować po wynikach.
Sprawdź się
Jakie kodowanie znaków należy podać w DSN połączenia PDO z MySQL?
utf8mb4 to pełne kodowanie UTF-8 obsługujące 4-bajtowe znaki (emoji, niektóre języki azjatyckie). Słowo kluczowe 'utf8' w MySQL to historycznie 3 bajty — nie jest prawdziwym UTF-8.
Który atrybut PDO powoduje, że błędy SQL są zgłaszane jako wyjątki?
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION sprawia, że każdy błąd SQL (zły SELECT, brak tabeli itp.) rzuca wyjątek PDOException zamiast cicho zwracać false.
Dlaczego NIE należy wyświetlać komunikatu $e->getMessage() z PDOException bezpośrednio użytkownikowi?
Komunikaty błędów PDO często zawierają nazwę bazy danych, użytkownika, tabelę lub strukturę zapytania. Ujawnianie ich pomaga atakującemu. Loguj błędy po stronie serwera, a użytkownikowi pokazuj ogólny komunikat.
Ćwiczenie
Napisz funkcję polaczDB(): PDO, która zwraca obiekt PDO połączony z bazą moja_aplikacja na localhost, z użytkownikiem root i pustym hasłem. Ustaw ERRMODE_EXCEPTION, FETCH_ASSOC i charset utf8mb4. Obsłuż błąd połączenia przez przechwycenie PDOException i wypisanie 'Błąd połączenia z bazą danych.' (bez szczegółów) — a następnie exit().
Pokaż rozwiązanie
<?php
declare(strict_types=1);
function polaczDB(): PDO
{
$dsn = 'mysql:host=localhost;dbname=moja_aplikacja;charset=utf8mb4';
try {
$pdo = new PDO($dsn, 'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (PDOException $e) {
// Loguj szczegóły po stronie serwera (nie pokazuj użytkownikowi)
error_log('PDO connect: ' . $e->getMessage());
echo 'Błąd połączenia z bazą danych.';
exit();
}
return $pdo;
}