Teoria — kluczowe pojęcia
MySQL / MariaDB
Relacyjna baza danych (RDBMS). Przechowuje dane w tabelach powiązanych kluczami obcymi. Dostępna w XAMPP jako MariaDB.
SQL
Język komunikacji z bazą. SELECT, INSERT, UPDATE, DELETE — przez PHP wysyłamy te zapytania do bazy i odbieramy wyniki.
PDO
PHP Data Objects — nowoczesny, bezpieczny interfejs do baz danych. Zalecany zamiast starszego mysqli_*.
Prepared Statements
Przygotowane zapytania z placeholderami (? lub :nazwa). Chronią przed SQL Injection.
Przepływ danych: PHP → PDO → MySQL
PHP wysyła zapytanie SQL przez PDO → MariaDB przetwarza → zwraca wynik jako tablicę PHP
Baza danych GameVault — inicjalizacja i struktura
Zanim zaczniesz pisać PHP, musisz mieć działającą bazę danych. Przejdź przez wszystkie 4 kroki poniżej — od uruchomienia XAMPP aż do gotowej bazy z danymi testowymi.
Przechowuje konta graczy. gracz_id to klucz główny — unikalny identyfikator każdego gracza. nick jest UNIQUE — dwóch graczy nie może mieć tego samego nicku.
Katalog gier w sklepie. Każda gra ma swój gra_id. gatunek pozwala filtrować gry (RPG, FPS, Puzzle...). wydawca_id to klucz obcy do tabeli wydawcy.
Tabela łącząca graczy z grami — realizuje relację wiele-do-wielu. Jeden gracz może kupić wiele gier, jedna gra może być kupiona przez wielu graczy. Przechowuje też historyczną cenę zakupu.
-- ═══════════════════════════════════════════════════
-- GAMEVAULT — inicjalizacja bazy danych
-- Wklej CAŁY ten kod do phpMyAdmin → zakładka SQL
-- ═══════════════════════════════════════════════════
-- Wybierz bazę (musi być wcześniej utworzona w phpMyAdmin)
USE gamevault;
-- ─── Czyszczenie (na wypadek ponownego uruchomienia skryptu) ───
DROP TABLE IF EXISTS zakupy; -- najpierw tabele podrzędne (mają FK)
DROP TABLE IF EXISTS gry;
DROP TABLE IF EXISTS gracze;
DROP TABLE IF EXISTS wydawcy;
-- ─── Tabela: wydawcy ───────────────────────────────────────────
-- Przechowuje firmy wydające gry (CD Projekt, Valve itd.)
-- wydawca_id: klucz główny, AUTO_INCREMENT = baza nadaje ID sama
CREATE TABLE wydawcy (
wydawca_id INT PRIMARY KEY AUTO_INCREMENT,
nazwa VARCHAR(100) NOT NULL,
kraj VARCHAR(3)
);
-- ─── Tabela: gracze ────────────────────────────────────────────
-- Konta użytkowników sklepu GameVault
-- nick: UNIQUE — nie można mieć dwóch graczy z tym samym nickiem
CREATE TABLE gracze (
gracz_id INT PRIMARY KEY AUTO_INCREMENT,
nick VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100),
kraj VARCHAR(3)
);
-- ─── Tabela: gry ───────────────────────────────────────────────
-- Katalog gier w sklepie
-- wydawca_id: FOREIGN KEY — musi istnieć w tabeli wydawcy
-- CHECK: cena nie może być ujemna
CREATE TABLE gry (
gra_id INT PRIMARY KEY AUTO_INCREMENT,
tytul VARCHAR(100) NOT NULL,
gatunek VARCHAR(50),
cena DECIMAL(7,2) NOT NULL CHECK (cena >= 0),
wydawca_id INT,
FOREIGN KEY (wydawca_id) REFERENCES wydawcy(wydawca_id)
);
-- ─── Tabela: zakupy ────────────────────────────────────────────
-- Tabela łącząca gracze ↔ gry (relacja wiele-do-wielu)
-- Jeden gracz może kupić wiele gier, jedna gra — wielu graczy
-- ON DELETE CASCADE: usunięcie gracza usuwa też jego zakupy
CREATE TABLE zakupy (
zakup_id INT PRIMARY KEY AUTO_INCREMENT,
gracz_id INT NOT NULL,
gra_id INT NOT NULL,
data_zakupu DATE DEFAULT (CURRENT_DATE),
cena_zakupu DECIMAL(7,2) NOT NULL,
FOREIGN KEY (gracz_id) REFERENCES gracze(gracz_id) ON DELETE CASCADE,
FOREIGN KEY (gra_id) REFERENCES gry(gra_id)
);
-- ─── Dane testowe ──────────────────────────────────────────────
INSERT INTO wydawcy (nazwa, kraj) VALUES
('CD Projekt', 'PL'),
('Valve', 'US'),
('IndieSoft', 'DE');
INSERT INTO gry (tytul, gatunek, cena, wydawca_id) VALUES
('Cyberpunk 2077', 'RPG', 199.99, 1),
('Half-Life: Alyx', 'FPS', 149.99, 2),
('Portal 2', 'Puzzle', 39.99, 2),
('PixelDungeon', 'RPG', 19.99, 3),
('SpeedRacer VR', 'Racing', 89.99, 3);
INSERT INTO gracze (nick, email, kraj) VALUES
('ShadowWolf', 'wolf@gv.pl', 'PL'),
('NeonByte', 'neon@gv.us', 'US'),
('PixelQueen', 'queen@gv.de', 'DE'),
('NoobMaster', 'noob@gv.pl', 'PL'); -- ten gracz nie ma zakupów
INSERT INTO zakupy (gracz_id, gra_id, data_zakupu, cena_zakupu) VALUES
(1, 1, '2024-11-01', 199.99), -- ShadowWolf → Cyberpunk
(1, 3, '2024-11-15', 39.99), -- ShadowWolf → Portal 2
(2, 2, '2024-12-01', 149.99), -- NeonByte → Half-Life
(3, 4, '2025-01-10', 19.99), -- PixelQueen → PixelDungeon
(3, 1, '2025-01-20', 199.99); -- PixelQueen → Cyberpunk
-- ─── Weryfikacja (opcjonalne — sprawdź czy dane są poprawne) ───
SELECT * FROM gracze;
SELECT * FROM gry;
SELECT * FROM zakupy;
gracze, gry, zakupy, wydawcy.Ćwiczenia krok po kroku
polaczenie.php w folderze XAMPP (htdocs/gamevault/). Nawiąż połączenie z bazą gamevault przez PDO i wyświetl potwierdzenie.
<?php
// Dane połączeniowe — XAMPP domyślnie
$host = 'localhost';
$baza = 'gamevault';
$user = 'root';
$haslo = ''; // XAMPP: domyślnie puste
$charset = 'utf8mb4';
// DSN = Data Source Name
$dsn = "mysql:host=$host;dbname=$baza;charset=$charset";
// Opcje PDO — tryb błędów i format wyników
$opcje = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $user, $haslo, $opcje);
echo "✅ Połączono z bazą gamevault!";
} catch (PDOException $e) {
echo "❌ Błąd: " . $e->getMessage();
}
?>
C:\xampp\htdocs\gamevault\polaczenie.php i otwórz http://localhost/gamevault/polaczenie.php w przeglądarce.polaczenie.php możesz dołączać do innych plików przez require_once 'polaczenie.php'; — wtedy $pdo będzie dostępne w każdym pliku.query() i pętli while(fetch()).
<?php
require_once 'polaczenie.php'; // $pdo gotowy do użycia
// Zapytanie SQL — pobieramy wszystkich graczy
$stmt = $pdo->query('SELECT nick, kraj FROM gracze ORDER BY nick');
echo "<h2>🎮 Lista graczy GameVault</h2>";
echo "<ul>";
// Pobieramy wiersz po wierszu — każdy $row to tablica asocjacyjna
while ($row = $stmt->fetch()) {
echo "<li>{$row['nick']} ({$row['kraj']})</li>";
}
echo "</ul>";
?>
FETCH_ASSOC (ustawione w połączeniu) sprawia, że $row['nick'] działa — klucze to nazwy kolumn SQL.?gatunek=RPG). Użyj Prepared Statement żeby zabezpieczyć zapytanie przed SQL Injection.
"SELECT * WHERE gatunek='" . $_GET['g'] . "'" — to SQL Injection!<?php
require_once 'polaczenie.php';
// Pobieramy parametr z URL: ?gatunek=RPG
// Domyślna wartość 'RPG' jeśli parametru nie ma
$gatunek = $_GET['gatunek'] ?? 'RPG';
// Krok 1: PRZYGOTUJ zapytanie z placeholderem ?
$stmt = $pdo->prepare(
'SELECT tytul, cena FROM gry WHERE gatunek = ? ORDER BY cena DESC'
);
// Krok 2: WYKONAJ i przekaż wartość — PDO samo zabezpieczy dane
$stmt->execute([$gatunek]);
// Krok 3: POBIERZ wyniki
$gry = $stmt->fetchAll();
echo "<h2>Gry z gatunku: {$gatunek}</h2>";
if (empty($gry)) {
echo "<p>Brak gier w tym gatunku.</p>";
} else {
echo "<ul>";
foreach ($gry as $gra) {
echo "<li>{$gra['tytul']} — {$gra['cena']} zł</li>";
}
echo "</ul>";
}
?>
http://localhost/gamevault/gry_gatunku.php?gatunek=RPGZmień gatunek na FPS, Puzzle itp.
<?php
require_once 'polaczenie.php';
// Zapytanie SQL z JOIN — łączymy 3 tabele
$sql = '
SELECT g.nick AS gracz,
gr.tytul AS gra,
z.data_zakupu AS data,
z.cena_zakupu AS cena
FROM gracze g
JOIN zakupy z ON g.gracz_id = z.gracz_id
JOIN gry gr ON z.gra_id = gr.gra_id
ORDER BY z.data_zakupu DESC
';
$stmt = $pdo->query($sql);
$zakupy = $stmt->fetchAll(); // pobiera WSZYSTKIE wiersze naraz
echo '<table border="1" cellpadding="8">';
echo '<tr><th>Gracz</th><th>Gra</th><th>Data</th><th>Cena</th></tr>';
foreach ($zakupy as $r) {
echo "<tr>
<td>{$r['gracz']}</td>
<td>{$r['gra']}</td>
<td>{$r['data']}</td>
<td>{$r['cena']} zł</td>
</tr>";
}
echo '</table>';
?>
fetchAll() pobiera wszystkie wiersze na raz jako tablicę tablic — wygodne gdy chcesz liczyć wiersze lub wielokrotnie iterować po danych.:nazwa). Po dodaniu pobierz jego ID.
<?php
require_once 'polaczenie.php';
// Dane nowego gracza
$nowyGracz = [
'nick' => 'MegaBot',
'email' => 'bot@gamevault.pl',
'kraj' => 'PL',
];
// Przygotowanie zapytania z nazwanymi parametrami :nick :email :kraj
$stmt = $pdo->prepare(
'INSERT INTO gracze (nick, email, kraj) VALUES (:nick, :email, :kraj)'
);
// Wykonanie — tablica kluczy musi pasować do :parametrów
$stmt->execute($nowyGracz);
// Pobranie ID ostatnio wstawionego rekordu
$noweId = $pdo->lastInsertId();
echo "✅ Dodano gracza! ID = {$noweId}";
?>
:nick są czytelniejsze niż znaki zapytania ? — szczególnie przy wielu kolumnach.<?php
require_once 'polaczenie.php';
try {
// Przygotowanie zapytania UPDATE z parametrem gatunku
$stmt = $pdo->prepare(
'UPDATE gry SET cena = cena * 1.10 WHERE gatunek = ?'
);
$stmt->execute(['RPG']);
// rowCount() — liczba zmodyfikowanych wierszy
$ile = $stmt->rowCount();
echo "✅ Zaktualizowano {$ile} gier RPG (+10% ceny)";
} catch (PDOException $e) {
// W razie błędu — wyświetl komunikat (nie pokazuj w produkcji!)
echo "❌ Błąd zapytania: " . $e->getMessage();
}
?>
rowCount() zwraca liczbę wierszy dotkniętych przez UPDATE lub DELETE. Dla SELECT — użyj count($stmt->fetchAll()).Zadanie projektowe
Mini-aplikacja PHP — panel GameVault
Utwórz folder htdocs/gamevault/ i stwórz pliki PHP realizujące poniższe funkcje. Każdy plik ma mieć obsługę błędów (try/catch).
- 01
polaczenie.php— plik konfiguracyjny z połączeniem PDO, dołączany przezrequire_oncedo pozostałych. - 02
gracze.php— wyświetl wszystkich graczy jako tabelę HTML z kolumnami: nick, email, kraj. - 03
gry.php?gatunek=RPG— wyświetl gry z wybranego gatunku. Gatunek pobierz z parametru URL przez Prepared Statement. - 04
zakupy.php— wyświetl historię zakupów (JOIN trzech tabel): nick gracza, tytuł gry, data zakupu, cena. - 05Dodaj do
gracze.phplicznik: ile jest graczy łącznie (SELECT COUNT(*)) — wyświetl nad tabelą.
Każdy plik otwieraj przez http://localhost/gamevault/nazwa.php
Quiz — sprawdź wiedzę
mysqli_*?fetch() a fetchAll()?lastInsertId()?