18. PHP – łączenie z bazą i operacje SELECT


Bazy danych to fundament większości aplikacji webowych. Za każdym razem gdy:

  • Przeglądasz posty na Instagramie
  • Sprawdzasz ranking w grze online
  • Szukasz wiadomości na Discordzie
  • Oglądasz playlistę na Spotify

W tym materiale nauczysz się pobierać dane z bazy MySQL/MariaDB używając PHP. To podstawowa umiejętność, którą musisz opanować przed nauką modyfikowania danych (INSERT, UPDATE, DELETE – te operacje poznasz w następnym temacie).

Dane dostępowe do bazy

Niezależnie od wybranej metody, potrzebujesz 4 podstawowych informacji:

$serwer = 'localhost';       // Adres serwera (localhost lub 127.0.0.1)
$user = 'root';              // Nazwa użytkownika bazy
$pass = '';                  // Hasło (domyślnie puste w XAMPP)
$baza = 'nazwa_bazy';        // Nazwa Twojej bazy danych

UWAGA: W środowisku produkcyjnym (na prawdziwym serwerze):

  • Hasło NIE MOŻE być puste!
  • Użytkownik NIE POWINIEN być root
  • Dane logowania przechowuj w oddzielnym pliku (np. config.php)
  • Nigdy nie commituj danych dostępowych do GitHuba!

Przykład pliku config.php:

<?php
// config.php - NIE COMMITUJ tego pliku do repozytorium!

define('DB_HOST', 'localhost');
define('DB_USER', 'twoj_user');
define('DB_PASS', 'twoje_silne_haslo');
define('DB_NAME', 'nazwa_bazy');
?>

Użycie w kodzie:

<?php
require_once 'config.php';

$pdo = new PDO(
    "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
    DB_USER,
    DB_PASS
);
?>

Przygotowanie – Utworzenie przykładowej bazy

Przed rozpoczęciem nauki potrzebujesz bazy z przykładowymi danymi. Stworzymy bazę gaming_hub z tabelą graczy – będzie bardziej interesująca niż klasyczne „imię i nazwisko”

Krok 1: Otwórz phpMyAdmin

W przeglądarce wejdź na:

http://localhost/phpmyadmin

Krok 2: Utwórz nową bazę danych

  • Kliknij zakładkę „Bazy danych”
  • W polu „Utwórz bazę danych” wpisz: gaming_hub
  • Wybierz kodowanie: utf8mb4_unicode_ci (poprawniejsze sortowanie wielu języków, lepsza zgodność Unicode, obsługa polskich znaków; mb4 w utf8mb4 oznacza „maximum bytes 4” — czyli kodowanie UTF-8 obsługujące znaki zajmujące do 4 bajtów.)
  • Kliknij „Utwórz”

Krok 3: Utwórz tabelę z danymi

Kliknij na bazę gaming_hub, przejdź do zakładki SQL i wykonaj poniższy kod:

-- Tabela graczy
CREATE TABLE players (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100),
    level INT DEFAULT 1,
    coins INT DEFAULT 0,
    rank_name VARCHAR(20) DEFAULT 'Bronze',
    games_played INT DEFAULT 0,
    wins INT DEFAULT 0,
    losses INT DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_login TIMESTAMP NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Przykładowe dane graczy (15 rekordów)
INSERT INTO players (username, email, level, coins, rank_name, games_played, wins, losses, last_login) VALUES
('ProGamer_Mike', 'mike@example.com', 45, 12500, 'Diamond', 850, 520, 330, NOW()),
('xX_Anna_Xx', 'anna@example.com', 32, 8900, 'Platinum', 620, 380, 240, NOW()),
('NinjaKiller', 'ninja@example.com', 67, 23400, 'Master', 1240, 890, 350, NOW()),
('ShadowHunter', 'shadow@example.com', 28, 6700, 'Gold', 490, 270, 220, DATE_SUB(NOW(), INTERVAL 2 DAY)),
('DragonSlayer99', 'dragon@example.com', 19, 4200, 'Silver', 320, 160, 160, DATE_SUB(NOW(), INTERVAL 5 DAY)),
('CoolGamer', 'cool@example.com', 52, 15600, 'Diamond', 980, 610, 370, NOW()),
('LegendaryPlayer', 'legend@example.com', 89, 45000, 'Grandmaster', 2100, 1560, 540, NOW()),
('NoobMaster', 'noob@example.com', 8, 1200, 'Bronze', 85, 30, 55, DATE_SUB(NOW(), INTERVAL 10 DAY)),
('FastFingers', 'fast@example.com', 41, 11200, 'Platinum', 740, 450, 290, NOW()),
('SilentAssassin', 'silent@example.com', 36, 9800, 'Platinum', 680, 410, 270, DATE_SUB(NOW(), INTERVAL 1 DAY)),
('ThunderStrike', 'thunder@example.com', 24, 5600, 'Gold', 410, 220, 190, DATE_SUB(NOW(), INTERVAL 3 DAY)),
('IceQueen', 'ice@example.com', 55, 17800, 'Diamond', 1050, 670, 380, NOW()),
('FireStorm', 'fire@example.com', 15, 3100, 'Silver', 250, 120, 130, DATE_SUB(NOW(), INTERVAL 7 DAY)),
('MysticWarrior', 'mystic@example.com', 63, 21000, 'Master', 1180, 800, 380, NOW()),
('SpeedDemon', 'speed@example.com', 29, 7200, 'Gold', 520, 290, 230, DATE_SUB(NOW(), INTERVAL 4 DAY));

Struktura tabeli players:

KolumnaTypOpis
idINTUnikalny identyfikator (AUTO_INCREMENT)
usernameVARCHAR(50)Nick gracza (unikalny)
emailVARCHAR(100)Adres email
levelINTPoziom gracza
coinsINTWirtualna waluta
rank_nameVARCHAR(20)Ranga (Bronze, Silver, Gold, Platinum, Diamond, Master, Grandmaster)
games_playedINTLiczba rozegranych gier
winsINTLiczba wygranych
lossesINTLiczba przegranych
created_atTIMESTAMPData utworzenia konta
last_loginTIMESTAMPOstatnie logowanie

Sposoby połączenia z bazą danych

PHP oferuje 2 nowoczesne sposoby komunikacji z MySQL, podejście proceduralne pomijamy jako przestarzałe.

1. MySQLi – Podejście obiektowe

Wykorzystujemy klasę mysqli.

$db = new mysqli($serwer, $user, $pass, $baza);

Podejście dobre, ale tylko dla MySQL/MariaDB

2. PDO – PHP Data Objects (ZALECANE!)

$pdo = new PDO("mysql:host=$serwer;dbname=$baza;charset=utf8mb4", $user, $pass);

Podejście uniwersalne, bezpieczne, nowoczesne

Łączenie z bazą danych

MySQLi – Podejście obiektowe

<?php
// Dane dostępowe
$serwer = 'localhost';
$user = 'root';
$pass = '';
$baza = 'gaming_hub';

// Wyłączamy automatyczne raportowanie błędów
mysqli_report(MYSQLI_REPORT_OFF);

// Połączenie obiektowe (@ ukrywa ostrzeżenia)
$db = @new mysqli($serwer, $user, $pass, $baza);

// Sprawdzenie połączenia
if ($db->connect_error) {
    die("Błąd połączenia: {$db->connect_error}");
}

echo 'Połączenie nawiązano!<br>';

// Ustawienie kodowania (ważne dla polskich znaków i emoji!)
$db->set_charset('utf8mb4');

// ... wykonywanie zapytań ...

// Zamknięcie połączenia
$db->close();
?>

Dostępne właściwości do obsługi błędów:

$db->connect_error   // Opis błędu połączenia
$db->connect_errno   // Kod błędu połączenia
$db->error           // Opis ostatniego błędu
$db->errno           // Kod ostatniego błędu

PDO – PHP Data Objects

<?php
// Dane dostępowe
$serwer = 'localhost';
$user = 'root';
$pass = '';
$baza = 'gaming_hub';

// Opcje PDO dla bezpieczeństwa i wygody
$opcje = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,        // Włącz wyjątki
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,   // Domyślnie tablica asocjacyjna
    PDO::ATTR_EMULATE_PREPARES => false,                // Prawdziwe prepared statements
];

try {
    // Połączenie PDO
    $pdo = new PDO(
        "mysql:host=$serwer;dbname=$baza;charset=utf8mb4",
        $user,
        $pass,
        $opcje
    );
    
    echo 'Połączenie nawiązano!<br>';
    
    // ... wykonywanie zapytań ...
    
} catch (PDOException $e) {
    // W prawdziwej aplikacji NIE pokazuj szczegółów błędu użytkownikowi!
    error_log("DB Error: {$e->getMessage()}");
    die('Nie można połączyć się z bazą danych. Spróbuj później.');
}

// Zamknięcie połączenia
$pdo = null;
?>
  • PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION – rzuca wyjątki przy błędach (łatwiej debugować)
  • PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC – zwraca tablice asocjacyjne (czytelniejsze)
  • PDO::ATTR_EMULATE_PREPARES => false – prawdziwe prepared statements (bezpieczniejsze)

Zamykanie połączenia z bazą

Operacja zamknięcia połączenia jest automatycznie wykonywana po zakończeniu skryptu. Jeżeli potrzebujemy zakończyć połączenie wcześniej:

MySQLi obiektowo:

$db->close();

PDO:

$pdo = null;
// lub
unset($pdo);

W praktyce: Rzadko musisz ręcznie zamykać połączenie – PHP robi to automatycznie po zakończeniu skryptu, ale na egzaminie zawodowym jest to wymagane.

SELECT – Pobieranie wszystkich rekordów

UWAGA: stosowanie query() nie zapewnia bezpieczeństwa w przypadku ataków SQJ Injection. Zamiast query należy używać parameterized prepared statements (opracowanie dalej w temacie). Aby query() było bezpieczne należy stosować mysqli_real_escape_string().

Użycie samego query() jest dopuszczalne na egzaminie zawodowym, jednak w prawdziwych projektach ZAWSZE używaj prepared statements.

Przykład 1: MySQLi obiektowo – PODATNE NA ATAKI! – tak nie rób

<?php
// Połączenie z bazą
mysqli_report(MYSQLI_REPORT_OFF);
$db = @new mysqli('localhost', 'root', '', 'gaming_hub');

if ($db->connect_error) {
    die("Błąd połączenia: {$db->connect_error}");
}

$db->set_charset('utf8mb4');
echo 'Połączenie nawiązano<br><br>';

// Zapytanie SQL - pobierz wszystkich graczy
$sql = "SELECT * FROM players";
$wynik = $db->query($sql);

// Sprawdzamy liczbę rekordów
$ile_wierszy = $wynik->num_rows;
echo "Znaleziono graczy: <strong>{$ile_wierszy}</strong><br><br>";

// Wyświetlamy w tabeli
echo '<table border="1" cellpadding="10" cellspacing="0">';
echo '<tr>
        <th>ID</th>
        <th>Username</th>
        <th>Level</th>
        <th>Coins</th>
        <th>Rank</th>
        <th>Wins/Losses</th>
      </tr>';

// Pętla while - pobieramy kolejne rekordy
while ($wiersz = $wynik->fetch_assoc()) {
    $username = htmlspecialchars($wiersz['username']);
    $coins = number_format($wiersz['coins']);
    
    echo "<tr>";
    echo "<td>{$wiersz['id']}</td>";
    echo "<td>{$username}</td>";
    echo "<td>{$wiersz['level']}</td>";
    echo "<td>{$coins}</td>";
    echo "<td>{$wiersz['rank_name']}</td>";
    echo "<td>{$wiersz['wins']}/{$wiersz['losses']}</td>";
    echo "</tr>";
}

echo '</table>';

// Zamknięcie połączenia
$db->close();
?>

Alternatywnie – pętla foreach

<?php
// ... (połączenie jak wyżej)

$sql = "SELECT * FROM players";
$wynik = $db->query($sql);

echo "Znaleziono graczy: {$wynik->num_rows}<br><br>";

foreach ($wynik as $row) {
    $username = htmlspecialchars($row['username']);
    echo "{$row['id']} - {$username} (Level: {$row['level']})<br>";
}

$db->close();
?>

Przykład 1: PDO – PODATNE NA ATAKI! – tak nie rób

<?php
try {
    // Połączenie PDO
    $pdo = new PDO(
        'mysql:host=localhost;dbname=gaming_hub;charset=utf8mb4',
        'root',
        '',
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
    );
    
    echo 'Połączenie nawiązano<br><br>';
    
    // Zapytanie SQL
    $sql = "SELECT * FROM players";
    $stmt = $pdo->query($sql);
    
    // Pobranie wszystkich wyników
    $wyniki = $stmt->fetchAll();
    
    $liczba_graczy = count($wyniki);
    echo "Znaleziono graczy: <strong>{$liczba_graczy}</strong><br><br>";
    
    // Wyświetlanie w tabeli HTML
    echo '<table border="1" cellpadding="10" cellspacing="0">';
    echo '<tr>
            <th>ID</th>
            <th>Username</th>
            <th>Level</th>
            <th>Coins</th>
            <th>Rank</th>
            <th>Games</th>
            <th>Win Rate</th>
          </tr>';
    
    foreach ($wyniki as $wiersz) {
        // Obliczamy Win Rate (procent wygranych)
        $winRate = $wiersz['games_played'] > 0 
            ? round(($wiersz['wins'] / $wiersz['games_played']) * 100, 1) 
            : 0;
        
        $username = htmlspecialchars($wiersz['username']);
        $coins = number_format($wiersz['coins']);
        
        echo "<tr>";
        echo "<td>{$wiersz['id']}</td>";
        echo "<td>{$username}</td>";
        echo "<td>{$wiersz['level']}</td>";
        echo "<td>{$coins}</td>";
        echo "<td>{$wiersz['rank_name']}</td>";
        echo "<td>{$wiersz['games_played']}</td>";
        echo "<td>{$winRate}%</td>";
        echo "</tr>";
    }
    
    echo '</table>';
    
} catch (PDOException $e) {
    error_log("DB Error: {$e->getMessage()}");
    die('Błąd bazy danych');
}

// Zamknięcie połączenia
$pdo = null;
?>

BEZPIECZEŃSTWO – SQL Injection

SQL Injection to technika ataku, w której hacker wstrzykuje złośliwy kod SQL przez dane wejściowe (formularze, URL). To najczęstszy typ ataku na aplikacje webowe!

NIEBEZPIECZNY KOD (NIGDY TAK NIE RÓB!)

<?php
// PODATNE NA SQL INJECTION!

$username = $_POST['username'];

// Niebezpieczne zapytanie - łączenie stringów!
$sql = "SELECT * FROM players WHERE username = '$username'";
$wynik = $db->query($sql);

/*
PROBLEM: Użytkownik może wpisać złośliwy kod!
*/
?>

Jak działa atak SQL Injection?

Scenariusz 1: Normalny użytkownik
---------------------------------
Użytkownik wpisuje:         ProGamer_Mike
Zapytanie SQL:              SELECT * FROM players WHERE username = 'ProGamer_Mike'
Wynik:                      Zwraca dane gracza


Scenariusz 2: Hacker - ujawnienie wszystkich danych
---------------------------------------------------
Hacker wpisuje:             ' OR '1'='1
Zapytanie SQL:              SELECT * FROM players WHERE username = '' OR '1'='1'
Wynik:                      Zwraca WSZYSTKICH graczy!
Dlaczego?                   '1'='1' jest zawsze prawdą, więc warunek zawsze się spełnia


Scenariusz 3: Hacker - usunięcie tabeli
----------------------------------------
Hacker wpisuje:             '; DROP TABLE players; --
Zapytanie SQL:              SELECT * FROM players WHERE username = ''; DROP TABLE players; --'
Wynik:                      USUWA CAŁĄ TABELĘ! 
Dlaczego?                   Średnik (;) kończy pierwsze zapytanie, DROP TABLE usuwa tabelę,
                           -- komentuje resztę zapytania


Scenariusz 4: Hacker - dodanie administratora
----------------------------------------------
Hacker wpisuje:             '; INSERT INTO users (username, role) VALUES ('hacker', 'admin'); --
Wynik:                      Dodaje sobie konto z uprawnieniami admina! 

Rzeczywiste konsekwencje SQL Injection:

  • Kradzież danych – haker może pobrać hasła, emaile, dane kart kredytowych
  • Usunięcie danych – cała baza może zostać skasowana
  • Modyfikacja danych – zmiana cen produktów, uprawnień użytkowników
  • Przejęcie kontroli – dodanie konta administratora
  • Kara prawna i finansowa – naruszenie RODO, utrata reputacji firmy

BEZPIECZNY KOD – Prepared Statements

BEZPIECZNY KOD – Prepared Statements

Prepared Statements (zapytania przygotowane) to mechanizm, który rozdziela kod SQL od danych. Baza danych najpierw „przygotowuje” zapytanie, a potem osobno przekazuje dane.

Dlaczego są bezpieczne?

  • Separacja kodu i danych – SQL i dane są wysyłane osobno
  • Automatyczne escapowanie – baza wie, że to dane, nie kod
  • Niemożliwy injection – dane nie mogą zmienić struktury zapytania

Jak działają Prepared Statements?

Krok 1: Przygotowanie zapytania (prepare)
------------------------------------------
PHP do MySQL:  "Przygotuj zapytanie: SELECT * FROM players WHERE username = ?"
MySQL:         "OK, czekam na dane do znaku zapytania"


Krok 2: Wysłanie danych (execute/bind_param)
---------------------------------------------
PHP do MySQL:  "Podstaw wartość: ' OR '1'='1"
MySQL:         "OK, traktuję to jako TEKST, nie jako KOD SQL"


Krok 3: Wykonanie
-----------------
MySQL wykonuje: SELECT * FROM players WHERE username = '\' OR \'1\'=\'1\''
                                                          ↑ Escapowane apostrofy
Wynik:          Szuka gracza o nicku "' OR '1'='1" (nie znajdzie takiego)
                NIE WYKONUJE ataku!

Przykład działania – porównanie

// BEZ prepared statements (niebezpieczne):
$username = "' OR '1'='1";
$sql = "SELECT * FROM players WHERE username = '$username'";
// Zapytanie: SELECT * FROM players WHERE username = '' OR '1'='1'
// ATAK SKUTECZNY!


// Z prepared statements (bezpieczne):
$username = "' OR '1'='1";
$stmt = $pdo->prepare("SELECT * FROM players WHERE username = :username");
$stmt->execute(['username' => $username]);
// Zapytanie: SELECT * FROM players WHERE username = '\' OR \'1\'=\'1\''
// Szuka gracza o nicku "' OR '1'='1" - nie znajdzie
// ATAK ZABLOKOWANY!

SELECT z warunkiem WHERE – dane z formularza ($_POST)

Bezpieczne podejście z prepared statements – tak robimy.

Przykład 1: Pobierz wszystkich graczy (prepared statements) – MySQLi obiektowo:

<?php
// Połączenie z bazą
mysqli_report(MYSQLI_REPORT_OFF);
$db = @new mysqli('localhost', 'root', '', 'gaming_hub');

if ($db->connect_error) {
    die("Błąd połączenia: {$db->connect_error}");
}

$db->set_charset('utf8mb4');
echo 'Połączenie nawiązano<br><br>';

// BEZPIECZNE - Prepared statement
// Dla SELECT * bez parametrów też używamy prepare() (dobra praktyka)
$stmt = $db->prepare("SELECT * FROM players");
$stmt->execute();

// Pobranie wyniku
$wynik = $stmt->get_result();

// Sprawdzamy liczbę rekordów
$ile_wierszy = $wynik->num_rows;
echo "Znaleziono graczy: <strong>{$ile_wierszy}</strong><br><br>";

// Wyświetlamy w tabeli
echo '<table border="1" cellpadding="10" cellspacing="0">';
echo '<tr>
        <th>ID</th>
        <th>Username</th>
        <th>Level</th>
        <th>Coins</th>
        <th>Rank</th>
        <th>Wins/Losses</th>
      </tr>';

// Pętla while - pobieramy kolejne rekordy
while ($wiersz = $wynik->fetch_assoc()) {
    $username = htmlspecialchars($wiersz['username']);
    $coins = number_format($wiersz['coins']);
    
    echo "<tr>";
    echo "<td>{$wiersz['id']}</td>";
    echo "<td>{$username}</td>";
    echo "<td>{$wiersz['level']}</td>";
    echo "<td>{$coins}</td>";
    echo "<td>{$wiersz['rank_name']}</td>";
    echo "<td>{$wiersz['wins']}/{$wiersz['losses']}</td>";
    echo "</tr>";
}

echo '</table>';

// Zamknięcie
$stmt->close();
$db->close();
?>

PDO:

<?php
try {
    // Połączenie PDO
    $pdo = new PDO(
        'mysql:host=localhost;dbname=gaming_hub;charset=utf8mb4',
        'root',
        '',
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false
        ]
    );
    
    echo 'Połączenie nawiązano<br><br>';
    
    // BEZPIECZNE - Prepared statement
    $stmt = $pdo->prepare("SELECT * FROM players");
    $stmt->execute();
    
    // Pobranie wszystkich wyników
    $wyniki = $stmt->fetchAll();
    
    $liczba_graczy = count($wyniki);
    echo "Znaleziono graczy: <strong>{$liczba_graczy}</strong><br><br>";
    
    // Wyświetlanie w tabeli HTML
    echo '<table border="1" cellpadding="10" cellspacing="0">';
    echo '<tr>
            <th>ID</th>
            <th>Username</th>
            <th>Level</th>
            <th>Coins</th>
            <th>Rank</th>
            <th>Games</th>
            <th>Win Rate</th>
          </tr>';
    
    foreach ($wyniki as $wiersz) {
        // Obliczamy Win Rate (procent wygranych)
        $winRate = $wiersz['games_played'] > 0 
            ? round(($wiersz['wins'] / $wiersz['games_played']) * 100, 1) 
            : 0;
        
        $username = htmlspecialchars($wiersz['username']);
        $coins = number_format($wiersz['coins']);
        
        echo "<tr>";
        echo "<td>{$wiersz['id']}</td>";
        echo "<td>{$username}</td>";
        echo "<td>{$wiersz['level']}</td>";
        echo "<td>{$coins}</td>";
        echo "<td>{$wiersz['rank_name']}</td>";
        echo "<td>{$wiersz['games_played']}</td>";
        echo "<td>{$winRate}%</td>";
        echo "</tr>";
    }
    
    echo '</table>';
    
} catch (PDOException $e) {
    error_log("DB Error: {$e->getMessage()}");
    die('Błąd bazy danych');
}

// Zamknięcie połączenia
$pdo = null;
?>
  • Używamy htmlspecialchars() dla bezpieczeństwa (ochrona przed XSS)
  • Używamy number_format() dla czytelności liczb (1234 → 1,234)
  • PDO automatycznie zwraca tablice asocjacyjne (ustawiliśmy w opcjach)

Przykład 2: Wyszukiwanie gracza po nicku (formularz POST)

MySQLi obiektowo

<?php
mysqli_report(MYSQLI_REPORT_OFF);
$db = @new mysqli('localhost', 'root', '', 'gaming_hub');

if ($db->connect_error) {
    die('Błąd połączenia');
}

$db->set_charset('utf8mb4');

// Sprawdzamy czy formularz został wysłany
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Pobieramy dane z formularza
    $username = trim($_POST['username'] ?? '');
    
    if (!empty($username)) {
        // BEZPIECZNE - Prepared statement
        $stmt = $db->prepare("SELECT * FROM players WHERE username = ?");
        
        // Bindowanie parametru (s = string)
        $stmt->bind_param("s", $username);
        
        // Wykonanie zapytania
        $stmt->execute();
        
        // Pobranie wyniku
        $wynik = $stmt->get_result();
        
        if ($gracz = $wynik->fetch_assoc()) {
            $safe_username = htmlspecialchars($gracz['username']);
            $safe_email = htmlspecialchars($gracz['email']);
            $coins = number_format($gracz['coins']);
            
            echo "<h2>Profil Gracza</h2>";
            echo "<strong>{$safe_username}</strong><br>";
            echo "Email: {$safe_email}<br>";
            echo "Level: {$gracz['level']}<br>";
            echo "Rank: {$gracz['rank_name']}<br>";
            echo "Coins: {$coins}<br>";
            echo "Games: {$gracz['games_played']} (W: {$gracz['wins']}, L: {$gracz['losses']})<br>";
        } else {
            $safe_search = htmlspecialchars($username);
            echo "Nie znaleziono gracza: {$safe_search}";
        }
        
        $stmt->close();
    } else {
        echo "Wpisz nick gracza!";
    }
}

$db->close();
?>

<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Wyszukaj gracza</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 50px auto;
            padding: 20px;
        }
        form {
            background: #f4f4f4;
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 20px;
        }
        input[type="text"] {
            padding: 10px;
            width: 70%;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        button {
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background: #0056b3;
        }
    </style>
</head>
<body>

<h1>Wyszukaj gracza</h1>

<form method="POST">
    <input type="text" name="username" placeholder="Wpisz nick gracza" value="<?= htmlspecialchars($_POST['username'] ?? '') ?>" required>
    <button type="submit" name="szukaj">Szukaj</button>
</form>

</body>
</html>

PDO

<?php
try {
    $pdo = new PDO(
        'mysql:host=localhost;dbname=gaming_hub;charset=utf8mb4',
        'root',
        '',
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false
        ]
    );
    
    // Sprawdzamy czy formularz został wysłany
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // Pobieramy dane z formularza
        $username = trim($_POST['username'] ?? '');
        
        if (!empty($username)) {
            // BEZPIECZNE - Prepared statement z named parameter
            $stmt = $pdo->prepare("SELECT * FROM players WHERE username = :username");
            $stmt->execute(['username' => $username]);
            
            $gracz = $stmt->fetch();
            
            if ($gracz) {
                $safe_username = htmlspecialchars($gracz['username']);
                $safe_email = htmlspecialchars($gracz['email']);
                $coins = number_format($gracz['coins']);
                
                echo "<h2>Profil Gracza</h2>";
                echo "<strong>{$safe_username}</strong><br>";
                echo "Email: {$safe_email}<br>";
                echo "Level: {$gracz['level']}<br>";
                echo "Rank: {$gracz['rank_name']}<br>";
                echo "Coins: {$coins}<br>";
                echo "Games: {$gracz['games_played']} (W: {$gracz['wins']}, L: {$gracz['losses']})<br>";
            } else {
                $safe_search = htmlspecialchars($username);
                echo "Nie znaleziono gracza: {$safe_search}";
            }
        } else {
            echo "Wpisz nick gracza!";
        }
    }
    
} catch (PDOException $e) {
    error_log("DB Error: {$e->getMessage()}");
    die('Błąd bazy danych');
}

$pdo = null;
?>

<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Wyszukaj gracza</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 50px auto;
            padding: 20px;
        }
        form {
            background: #f4f4f4;
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 20px;
        }
        input[type="text"] {
            padding: 10px;
            width: 70%;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        button {
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background: #0056b3;
        }
    </style>
</head>
<body>

<h1>Wyszukaj gracza</h1>

<form method="POST">
    <input type="text" name="username" placeholder="Wpisz nick gracza" value="<?= htmlspecialchars($_POST['username'] ?? '') ?>" required>
    <button type="submit" name="szukaj">Szukaj</button>
</form>

</body>
</html>

SELECT z warunkiem WHERE – dane z URL ($_GET)

Przykład 3: Profil gracza przez URL (GET)

Adres URL: profil.php?id=5 lub profil.php?username=ProGamer_Mike

MySQLi obiektowo:

<?php
// profil.php
mysqli_report(MYSQLI_REPORT_OFF);
$db = @new mysqli('localhost', 'root', '', 'gaming_hub');

if ($db->connect_error) {
    die('Błąd połączenia');
}

$db->set_charset('utf8mb4');

if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['id'])) {
    // Pobieramy ID z URL
    $player_id = (int)$_GET['id'];
    
    if ($player_id > 0) {
        // BEZPIECZNE - Prepared statement
        $stmt = $db->prepare("SELECT * FROM players WHERE id = ?");
        $stmt->bind_param("i", $player_id);  // i = integer
        $stmt->execute();
        
        $wynik = $stmt->get_result();
        
        if ($gracz = $wynik->fetch_assoc()) {
            $username = htmlspecialchars($gracz['username']);
            $email = htmlspecialchars($gracz['email']);
            $coins = number_format($gracz['coins']);
            
            echo "<h1>Profil gracza</h1>";
            echo "<div style='background:#f4f4f4; padding:20px; border-radius:8px;'>";
            echo "<h2>{$username}</h2>";
            echo "<p>Email: {$email}</p>";
            echo "<p>Level: <strong>{$gracz['level']}</strong></p>";
            echo "<p>Rank: <strong>{$gracz['rank_name']}</strong></p>";
            echo "<p>Coins: <strong>{$coins}</strong></p>";
            echo "<p>Rozegrane gry: {$gracz['games_played']}</p>";
            echo "<p>Wygrane: {$gracz['wins']}</p>";
            echo "<p>Przegrane: {$gracz['losses']}</p>";
            
            // Win Rate
            $winRate = $gracz['games_played'] > 0 
                ? round(($gracz['wins'] / $gracz['games_played']) * 100, 1) 
                : 0;
            echo "<p>Win Rate: <strong>{$winRate}%</strong></p>";
            echo "</div>";
        } else {
            echo "Nie znaleziono gracza o ID: {$player_id}";
        }
        
        $stmt->close();
    } else {
        echo "Nieprawidłowe ID gracza";
    }
} else {
    echo "Podaj ID gracza w URL (np. profil.php?id=5)";
}

$db->close();

// Lista linków do innych graczy
echo "<br><hr><h3>Inni gracze:</h3>";
echo "<a href='profil.php?id=1'>Gracz #1</a> | ";
echo "<a href='profil.php?id=2'>Gracz #2</a> | ";
echo "<a href='profil.php?id=3'>Gracz #3</a> | ";
echo "<a href='profil.php?id=7'>Gracz #7</a>";
?>

PDO:

<?php
// profil.php
try {
    $pdo = new PDO(
        'mysql:host=localhost;dbname=gaming_hub;charset=utf8mb4',
        'root',
        '',
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false
        ]
    );
    
    // sprawdzamy metodę HTTP i parametr
    if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['id'])) {
        // Pobieramy ID z URL
        $player_id = (int)$_GET['id'];
        
        if ($player_id > 0) {
            // BEZPIECZNE - Prepared statement
            $stmt = $pdo->prepare("SELECT * FROM players WHERE id = :id");
            $stmt->execute(['id' => $player_id]);
            
            $gracz = $stmt->fetch();
            
            if ($gracz) {
                $username = htmlspecialchars($gracz['username']);
                $email = htmlspecialchars($gracz['email']);
                $coins = number_format($gracz['coins']);
                
                echo "<h1>Profil gracza</h1>";
                echo "<div style='background:#f4f4f4; padding:20px; border-radius:8px;'>";
                echo "<h2>{$username}</h2>";
                echo "<p>Email: {$email}</p>";
                echo "<p>Level: <strong>{$gracz['level']}</strong></p>";
                echo "<p>Rank: <strong>{$gracz['rank_name']}</strong></p>";
                echo "<p>Coins: <strong>{$coins}</strong></p>";
                echo "<p>Rozegrane gry: {$gracz['games_played']}</p>";
                echo "<p>Wygrane: {$gracz['wins']}</p>";
                echo "<p>Przegrane: {$gracz['losses']}</p>";
                
                // Win Rate
                $winRate = $gracz['games_played'] > 0 
                    ? round(($gracz['wins'] / $gracz['games_played']) * 100, 1) 
                    : 0;
                echo "<p>Win Rate: <strong>{$winRate}%</strong></p>";
                echo "</div>";
            } else {
                echo "Nie znaleziono gracza o ID: {$player_id}";
            }
        } else {
            echo "Nieprawidłowe ID gracza";
        }
    } else {
        echo "Podaj ID gracza w URL (np. profil.php?id=5)";
    }
    
} catch (PDOException $e) {
    error_log("DB Error: {$e->getMessage()}");
    die('Błąd bazy danych');
}

$pdo = null;

// Lista linków do innych graczy
echo "<br><hr><h3>Inni gracze:</h3>";
echo "<a href='profil.php?id=1'>Gracz #1</a> | ";
echo "<a href='profil.php?id=2'>Gracz #2</a> | ";
echo "<a href='profil.php?id=3'>Gracz #3</a> | ";
echo "<a href='profil.php?id=7'>Gracz #7</a>";
?>

Przykład 4: Wyszukiwarka z wieloma filtrami (formularz POST)

MySQLi obiektowo:

<?php
mysqli_report(MYSQLI_REPORT_OFF);
$db = @new mysqli('localhost', 'root', '', 'gaming_hub');

if ($db->connect_error) {
    die('Błąd połączenia');
}

$db->set_charset('utf8mb4');

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Pobieramy dane z formularza
    $username = trim($_POST['username'] ?? '');
    $rank = $_POST['rank'] ?? '';
    $min_level = (int)($_POST['min_level'] ?? 0);
    
    // Budujemy zapytanie dynamicznie (BEZPIECZNIE!)
    $sql = "SELECT * FROM players WHERE 1=1";
    $types = "";
    $params = [];
    
    if (!empty($username)) {
        $sql .= " AND username LIKE ?";
        $types .= "s";
        $params[] = "%{$username}%";
    }
    
    if (!empty($rank)) {
        $sql .= " AND rank_name = ?";
        $types .= "s";
        $params[] = $rank;
    }
    
    if ($min_level > 0) {
        $sql .= " AND level >= ?";
        $types .= "i";
        $params[] = $min_level;
    }
    
    $sql .= " ORDER BY level DESC";
    
    // Przygotowanie i wykonanie zapytania
    $stmt = $db->prepare($sql);
    
    if (!empty($params)) {
        $stmt->bind_param($types, ...$params);
    }
    
    $stmt->execute();
    $wynik = $stmt->get_result();
    
    // Wyświetlamy wyniki
    if ($wynik->num_rows > 0) {
        echo "<h3>Znaleziono: {$wynik->num_rows} graczy</h3>";
        
        echo '<table border="1" cellpadding="10" style="width:100%; border-collapse:collapse;">';
        echo '<tr style="background:#007bff; color:white;">
                <th>Username</th>
                <th>Level</th>
                <th>Rank</th>
                <th>Coins</th>
                <th>Games</th>
              </tr>';
        
        while ($gracz = $wynik->fetch_assoc()) {
            $safe_username = htmlspecialchars($gracz['username']);
            $coins = number_format($gracz['coins']);
            
            echo "<tr>";
            echo "<td>{$safe_username}</td>";
            echo "<td>{$gracz['level']}</td>";
            echo "<td>{$gracz['rank_name']}</td>";
            echo "<td>{$coins}</td>";
            echo "<td>{$gracz['games_played']}</td>";
            echo "</tr>";
        }
        
        echo '</table>';
    } else {
        echo "<p>Nie znaleziono graczy spełniających kryteria</p>";
    }
    
    $stmt->close();
}

$db->close();
?>

<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Wyszukiwarka graczy</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 1000px;
            margin: 50px auto;
            padding: 20px;
        }
        form {
            background: #f4f4f4;
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 20px;
        }
        input, select {
            padding: 10px;
            margin: 5px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        button {
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background: #0056b3;
        }
        table {
            width: 100%;
            border-collapse: collapse;
        }
        tr:hover {
            background: #f5f5f5;
        }
    </style>
</head>
<body>

<h1>Wyszukiwarka Graczy</h1>

<form method="POST">
    <input type="text" name="username" placeholder="Nick gracza (fragment)" value="<?= htmlspecialchars($_POST['username'] ?? '') ?>">
    
    <select name="rank">
        <option value="">Wszystkie rangi</option>
        <option value="Bronze" <?= ($_POST['rank'] ?? '') == 'Bronze' ? 'selected' : '' ?>>Bronze</option>
        <option value="Silver" <?= ($_POST['rank'] ?? '') == 'Silver' ? 'selected' : '' ?>>Silver</option>
        <option value="Gold" <?= ($_POST['rank'] ?? '') == 'Gold' ? 'selected' : '' ?>>Gold</option>
        <option value="Platinum" <?= ($_POST['rank'] ?? '') == 'Platinum' ? 'selected' : '' ?>>Platinum</option>
        <option value="Diamond" <?= ($_POST['rank'] ?? '') == 'Diamond' ? 'selected' : '' ?>>Diamond</option>
        <option value="Master" <?= ($_POST['rank'] ?? '') == 'Master' ? 'selected' : '' ?>>Master</option>
        <option value="Grandmaster" <?= ($_POST['rank'] ?? '') == 'Grandmaster' ? 'selected' : '' ?>>Grandmaster</option>
    </select>
    
    <input type="number" name="min_level" placeholder="Min. poziom" min="1" value="<?= htmlspecialchars($_POST['min_level'] ?? '') ?>">
    
    <button type="submit" name="szukaj">Szukaj</button>
</form>

</body>
</html>

PDO:

<?php
try {
    $pdo = new PDO(
        'mysql:host=localhost;dbname=gaming_hub;charset=utf8mb4',
        'root',
        '',
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false
        ]
    );
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // Pobieramy dane z formularza
        $username = trim($_POST['username'] ?? '');
        $rank = $_POST['rank'] ?? '';
        $min_level = (int)($_POST['min_level'] ?? 0);
        
        // Budujemy zapytanie dynamicznie (BEZPIECZNIE!)
        $sql = "SELECT * FROM players WHERE 1=1";
        $params = [];
        
        if (!empty($username)) {
            $sql .= " AND username LIKE :username";
            $params['username'] = "%{$username}%";
        }
        
        if (!empty($rank)) {
            $sql .= " AND rank_name = :rank";
            $params['rank'] = $rank;
        }
        
        if ($min_level > 0) {
            $sql .= " AND level >= :min_level";
            $params['min_level'] = $min_level;
        }
        
        $sql .= " ORDER BY level DESC";
        
        // Wykonujemy zapytanie
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $wyniki = $stmt->fetchAll();
        
        // Wyświetlamy wyniki
        $liczba = count($wyniki);
        if ($liczba > 0) {
            echo "<h3>Znaleziono: {$liczba} graczy</h3>";
            
            echo '<table border="1" cellpadding="10" style="width:100%; border-collapse:collapse;">';
            echo '<tr style="background:#007bff; color:white;">
                    <th>Username</th>
                    <th>Level</th>
                    <th>Rank</th>
                    <th>Coins</th>
                    <th>Games</th>
                  </tr>';
            
            foreach ($wyniki as $gracz) {
                $safe_username = htmlspecialchars($gracz['username']);
                $coins = number_format($gracz['coins']);
                
                echo "<tr>";
                echo "<td>{$safe_username}</td>";
                echo "<td>{$gracz['level']}</td>";
                echo "<td>{$gracz['rank_name']}</td>";
                echo "<td>{$coins}</td>";
                echo "<td>{$gracz['games_played']}</td>";
                echo "</tr>";
            }
            
            echo '</table>';
        } else {
            echo "<p>Nie znaleziono graczy spełniających kryteria</p>";
        }
    }
    
} catch (PDOException $e) {
    error_log("DB Error: {$e->getMessage()}");
    die('Błąd bazy danych');
}

$pdo = null;
?>

<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <title>Wyszukiwarka graczy</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 1000px;
            margin: 50px auto;
            padding: 20px;
        }
        form {
            background: #f4f4f4;
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 20px;
        }
        input, select {
            padding: 10px;
            margin: 5px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        button {
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background: #0056b3;
        }
        table {
            width: 100%;
            border-collapse: collapse;
        }
        tr:hover {
            background: #f5f5f5;
        }
    </style>
</head>
<body>

<h1>Wyszukiwarka Graczy</h1>

<form method="POST">
    <input type="text" name="username" placeholder="Nick gracza (fragment)" value="<?= htmlspecialchars($_POST['username'] ?? '') ?>">
    
    <select name="rank">
        <option value="">Wszystkie rangi</option>
        <option value="Bronze" <?= ($_POST['rank'] ?? '') == 'Bronze' ? 'selected' : '' ?>>Bronze</option>
        <option value="Silver" <?= ($_POST['rank'] ?? '') == 'Silver' ? 'selected' : '' ?>>Silver</option>
        <option value="Gold" <?= ($_POST['rank'] ?? '') == 'Gold' ? 'selected' : '' ?>>Gold</option>
        <option value="Platinum" <?= ($_POST['rank'] ?? '') == 'Platinum' ? 'selected' : '' ?>>Platinum</option>
        <option value="Diamond" <?= ($_POST['rank'] ?? '') == 'Diamond' ? 'selected' : '' ?>>Diamond</option>
        <option value="Master" <?= ($_POST['rank'] ?? '') == 'Master' ? 'selected' : '' ?>>Master</option>
        <option value="Grandmaster" <?= ($_POST['rank'] ?? '') == 'Grandmaster' ? 'selected' : '' ?>>Grandmaster</option>
    </select>
    
    <input type="number" name="min_level" placeholder="Min. poziom" min="1" value="<?= htmlspecialchars($_POST['min_level'] ?? '') ?>">
    
    <button type="submit" name="szukaj">Szukaj</button>
</form>

</body>
</html>

Przykład 5: Ranking graczy – sortowanie i limit (GET)

Adres URL: ranking.php?sort=level&order=desc&limit=10

MySQLi obiektowo:

<?php
// ranking.php
mysqli_report(MYSQLI_REPORT_OFF);
$db = @new mysqli('localhost', 'root', '', 'gaming_hub');

if ($db->connect_error) {
    die('Błąd połączenia');
}

$db->set_charset('utf8mb4');

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    // Pobieramy parametry z URL
    $sort = $_GET['sort'] ?? 'level';
    $order = $_GET['order'] ?? 'desc';
    $limit = (int)($_GET['limit'] ?? 10);
    
    // Walidacja parametrów (whitelist - tylko dozwolone wartości)
    $allowed_sort = ['level', 'coins', 'wins', 'games_played'];
    $allowed_order = ['asc', 'desc'];
    
    if (!in_array($sort, $allowed_sort)) {
        $sort = 'level';
    }
    
    if (!in_array($order, $allowed_order)) {
        $order = 'desc';
    }
    
    if ($limit < 1 || $limit > 100) {
        $limit = 10;
    }
    
    // BEZPIECZNE - używamy walidacji + prepared statement dla limitu
    // Kolumny sortowania są z whitelisty, więc bezpieczne
    $sql = "SELECT * FROM players ORDER BY {$sort} {$order} LIMIT ?";
    $stmt = $db->prepare($sql);
    $stmt->bind_param("i", $limit);
    $stmt->execute();
    
    $wynik = $stmt->get_result();
    
    $sort_label = [
        'level' => 'poziomu',
        'coins' => 'coins',
        'wins' => 'wygranych',
        'games_played' => 'rozegranych gier'
    ];
    
    echo "<h1>Ranking graczy (wg {$sort_label[$sort]})</h1>";
    echo "<p>Top {$limit} graczy</p>";
    
    echo '<table border="1" cellpadding="10" style="width:100%; border-collapse:collapse;">';
    echo '<tr style="background:#007bff; color:white;">
            <th>#</th>
            <th>Username</th>
            <th>Level</th>
            <th>Rank</th>
            <th>Coins</th>
            <th>Wins</th>
            <th>Games</th>
          </tr>';
    
    $pozycja = 1;
    while ($gracz = $wynik->fetch_assoc()) {
        $username = htmlspecialchars($gracz['username']);
        $coins = number_format($gracz['coins']);
        
        echo "<tr>";
        echo "<td><strong>#{$pozycja}</strong></td>";
        echo "<td>{$username}</td>";
        echo "<td>{$gracz['level']}</td>";
        echo "<td>{$gracz['rank_name']}</td>";
        echo "<td>{$coins}</td>";
        echo "<td>{$gracz['wins']}</td>";
        echo "<td>{$gracz['games_played']}</td>";
        echo "</tr>";
        $pozycja++;
    }
    
    echo '</table>';
    
    echo "<br><p>Zmień sortowanie:</p>";
    echo "<a href='ranking.php?sort=level&order=desc&limit=10'>Top 10 Level</a> | ";
    echo "<a href='ranking.php?sort=coins&order=desc&limit=5'>Top 5 Coins</a> | ";
    echo "<a href='ranking.php?sort=wins&order=desc&limit=20'>Top 20 Wins</a>";
    
    $stmt->close();
}

$db->close();
?>

PDO:

<?php
// ranking.php
try {
    $pdo = new PDO(
        'mysql:host=localhost;dbname=gaming_hub;charset=utf8mb4',
        'root',
        '',
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false
        ]
    );
    
    if ($_SERVER['REQUEST_METHOD'] === 'GET') {
        // Pobieramy parametry z URL
        $sort = $_GET['sort'] ?? 'level';
        $order = $_GET['order'] ?? 'desc';
        $limit = (int)($_GET['limit'] ?? 10);
        
        // Walidacja parametrów (whitelist - tylko dozwolone wartości)
        $allowed_sort = ['level', 'coins', 'wins', 'games_played'];
        $allowed_order = ['asc', 'desc'];
        
        if (!in_array($sort, $allowed_sort)) {
            $sort = 'level';
        }
        
        if (!in_array($order, $allowed_order)) {
            $order = 'desc';
        }
        
        if ($limit < 1 || $limit > 100) {
            $limit = 10;
        }
        
        // BEZPIECZNE - używamy walidacji + prepared statement dla limitu
        // Kolumny sortowania są z whitelisty, więc bezpieczne
        $sql = "SELECT * FROM players ORDER BY {$sort} {$order} LIMIT :limit";
        $stmt = $pdo->prepare($sql);
        $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
        $stmt->execute();
        
        $wyniki = $stmt->fetchAll();
        
        $sort_label = [
            'level' => 'poziomu',
            'coins' => 'coins',
            'wins' => 'wygranych',
            'games_played' => 'rozegranych gier'
        ];
        
        echo "<h1>Ranking graczy (wg {$sort_label[$sort]})</h1>";
        echo "<p>Top {$limit} graczy</p>";
        
        echo '<table border="1" cellpadding="10" style="width:100%; border-collapse:collapse;">';
        echo '<tr style="background:#007bff; color:white;">
                <th>#</th>
                <th>Username</th>
                <th>Level</th>
                <th>Rank</th>
                <th>Coins</th>
                <th>Wins</th>
                <th>Games</th>
              </tr>';
        
        $pozycja = 1;
        foreach ($wyniki as $gracz) {
            $username = htmlspecialchars($gracz['username']);
            $coins = number_format($gracz['coins']);
            
            echo "<tr>";
            echo "<td><strong>#{$pozycja}</strong></td>";
            echo "<td>{$username}</td>";
            echo "<td>{$gracz['level']}</td>";
            echo "<td>{$gracz['rank_name']}</td>";
            echo "<td>{$coins}</td>";
            echo "<td>{$gracz['wins']}</td>";
            echo "<td>{$gracz['games_played']}</td>";
            echo "</tr>";
            $pozycja++;
        }
        
        echo '</table>';
        
        echo "<br><p>Zmień sortowanie:</p>";
        echo "<a href='ranking.php?sort=level&order=desc&limit=10'>Top 10 Level</a> | ";
        echo "<a href='ranking.php?sort=coins&order=desc&limit=5'>Top 5 Coins</a> | ";
        echo "<a href='ranking.php?sort=wins&order=desc&limit=20'>Top 20 Wins</a>";
    }
    
} catch (PDOException $e) {
    error_log("DB Error: {$e->getMessage()}");
    die('Błąd bazy danych');
}

$pdo = null;
?>

Przykład 6: Wyszukiwanie LIKE – dane z GET

Adres URL: szukaj.php?q=Dragon

MySQLi obiektowo:

<?php
// szukaj.php
mysqli_report(MYSQLI_REPORT_OFF);
$db = @new mysqli('localhost', 'root', '', 'gaming_hub');

if ($db->connect_error) {
    die('Błąd połączenia');
}

$db->set_charset('utf8mb4');

// Pobieramy fraze z URL
$szukaj = trim($_GET['q'] ?? '');

if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['q'])) {
    // BEZPIECZNE - Prepared statement z LIKE
    $stmt = $db->prepare("SELECT * FROM players WHERE username LIKE ? OR email LIKE ?");
    
    // Dodajemy % do wyszukiwania
    $szukaj_wildcard = "%{$szukaj}%";
    $stmt->bind_param("ss", $szukaj_wildcard, $szukaj_wildcard);
    $stmt->execute();
    
    $wynik = $stmt->get_result();
    
    $safe_search = htmlspecialchars($szukaj);
    
    if ($wynik->num_rows > 0) {
        echo "<h2>Wyniki wyszukiwania: \"{$safe_search}\"</h2>";
        echo "<p>Znaleziono: {$wynik->num_rows} graczy</p><br>";
        
        while ($gracz = $wynik->fetch_assoc()) {
            $username = htmlspecialchars($gracz['username']);
            $email = htmlspecialchars($gracz['email']);
            $coins = number_format($gracz['coins']);
            
            echo "<div style='background:#f4f4f4; padding:15px; margin:10px 0; border-radius:8px;'>";
            echo "<strong>{$username}</strong> (Level: {$gracz['level']}, Rank: {$gracz['rank_name']})<br>";
            echo "{$email} | {$coins} coins<br>";
            echo "<a href='profil.php?id={$gracz['id']}'>Zobacz profil</a>";
            echo "</div>";
        }
    } else {
        echo "<p>Nie znaleziono graczy dla frazy: \"{$safe_search}\"</p>";
    }
    
    $stmt->close();
} else {
    echo "<p>Wpisz frazę do wyszukania (np. szukaj.php?q=Dragon)</p>";
}

$db->close();

echo "<br><hr>";
echo "<p>Przykłady wyszukiwań:</p>";
echo "<a href='szukaj.php?q=Dragon'>szukaj.php?q=Dragon</a><br>";
echo "<a href='szukaj.php?q=Gamer'>szukaj.php?q=Gamer</a><br>";
echo "<a href='szukaj.php?q=@example.com'>szukaj.php?q=@example.com</a>";
?>

PDO:

<?php
// szukaj.php
try {
    $pdo = new PDO(
        'mysql:host=localhost;dbname=gaming_hub;charset=utf8mb4',
        'root',
        '',
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false
        ]
    );
    
    // Pobieramy fraze z URL
    $szukaj = trim($_GET['q'] ?? '');
    
    if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['q'])) {
        // BEZPIECZNE - Prepared statement z LIKE
        $stmt = $pdo->prepare("SELECT * FROM players WHERE username LIKE :szukaj OR email LIKE :szukaj");
        
        // Dodajemy % do wyszukiwania
        $szukaj_wildcard = "%{$szukaj}%";
        $stmt->execute(['szukaj' => $szukaj_wildcard]);
        
        $wyniki = $stmt->fetchAll();
        
        $safe_search = htmlspecialchars($szukaj);
        $liczba = count($wyniki);
        
        if ($liczba > 0) {
            echo "<h2>Wyniki wyszukiwania: \"{$safe_search}\"</h2>";
            echo "<p>Znaleziono: {$liczba} graczy</p><br>";
            
            foreach ($wyniki as $gracz) {
                $username = htmlspecialchars($gracz['username']);
                $email = htmlspecialchars($gracz['email']);
                $coins = number_format($gracz['coins']);
                
                echo "<div style='background:#f4f4f4; padding:15px; margin:10px 0; border-radius:8px;'>";
                echo "<strong>{$username}</strong> (Level: {$gracz['level']}, Rank: {$gracz['rank_name']})<br>";
                echo "{$email} | {$coins} coins<br>";
                echo "<a href='profil.php?id={$gracz['id']}'>Zobacz profil</a>";
                echo "</div>";
            }
        } else {
            echo "<p>Nie znaleziono graczy dla frazy: \"{$safe_search}\"</p>";
        }
    } else {
        echo "<p>Wpisz frazę do wyszukania (np. szukaj.php?q=Dragon)</p>";
    }
    
} catch (PDOException $e) {
    error_log("DB Error: {$e->getMessage()}");
    die('Błąd bazy danych');
}

$pdo = null;

echo "<br><hr>";
echo "<p>Przykłady wyszukiwań:</p>";
echo "<a href='szukaj.php?q=Dragon'>szukaj.php?q=Dragon</a><br>";
echo "<a href='szukaj.php?q=Gamer'>szukaj.php?q=Gamer</a><br>";
echo "<a href='szukaj.php?q=@example.com'>szukaj.php?q=@example.com</a>";
?>

Przykład 7: Statystyki – funkcje agregujące

MySQLi obiektowo:

<?php
mysqli_report(MYSQLI_REPORT_OFF);
$db = @new mysqli('localhost', 'root', '', 'gaming_hub');

if ($db->connect_error) {
    die('Błąd połączenia');
}

$db->set_charset('utf8mb4');

// BEZPIECZNE - Prepared statement (brak parametrów użytkownika)
$stmt = $db->prepare("SELECT 
    COUNT(*) as total_players,
    AVG(level) as avg_level,
    SUM(coins) as total_coins,
    MAX(level) as max_level,
    MIN(level) as min_level,
    SUM(games_played) as total_games,
    SUM(wins) as total_wins,
    SUM(losses) as total_losses
FROM players");

$stmt->execute();
$wynik = $stmt->get_result();
$stats = $wynik->fetch_assoc();

$avg_level = round($stats['avg_level'], 1);
$total_coins = number_format($stats['total_coins']);
$total_games = number_format($stats['total_games']);
$total_wins = number_format($stats['total_wins']);
$total_losses = number_format($stats['total_losses']);

echo "<h2>Statystyki Gaming Hub</h2>";
echo "<div style='background:#f4f4f4; padding:20px; border-radius:8px;'>";
echo "<p>Liczba graczy: <strong>{$stats['total_players']}</strong></p>";
echo "<p>Średni poziom: <strong>{$avg_level}</strong></p>";
echo "<p>Łączne coins: <strong>{$total_coins}</strong></p>";
echo "<p>Najwyższy poziom: <strong>{$stats['max_level']}</strong></p>";
echo "<p>Najniższy poziom: <strong>{$stats['min_level']}</strong></p>";
echo "<p>Rozegrane gry: <strong>{$total_games}</strong></p>";
echo "<p>Wygrane: <strong>{$total_wins}</strong></p>";
echo "<p>Przegrane: <strong>{$total_losses}</strong></p>";
echo "</div>";

$stmt->close();
$db->close();
?>

PDO:

<?php
try {
    $pdo = new PDO(
        'mysql:host=localhost;dbname=gaming_hub;charset=utf8mb4',
        'root',
        '',
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false
        ]
    );
    
    // BEZPIECZNE - Prepared statement (brak parametrów użytkownika)
    $stmt = $pdo->prepare("SELECT 
        COUNT(*) as total_players,
        AVG(level) as avg_level,
        SUM(coins) as total_coins,
        MAX(level) as max_level,
        MIN(level) as min_level,
        SUM(games_played) as total_games,
        SUM(wins) as total_wins,
        SUM(losses) as total_losses
    FROM players");
    
    $stmt->execute();
    $stats = $stmt->fetch();
    
    $avg_level = round($stats['avg_level'], 1);
    $total_coins = number_format($stats['total_coins']);
    $total_games = number_format($stats['total_games']);
    $total_wins = number_format($stats['total_wins']);
    $total_losses = number_format($stats['total_losses']);
    
    echo "<h2>Statystyki Gaming Hub</h2>";
    echo "<div style='background:#f4f4f4; padding:20px; border-radius:8px;'>";
    echo "<p>Liczba graczy: <strong>{$stats['total_players']}</strong></p>";
    echo "<p>Średni poziom: <strong>{$avg_level}</strong></p>";
    echo "<p>ączne coins: <strong>{$total_coins}</strong></p>";
    echo "<p>Najwyższy poziom: <strong>{$stats['max_level']}</strong></p>";
    echo "<p>Najniższy poziom: <strong>{$stats['min_level']}</strong></p>";
    echo "<p>Rozegrane gry: <strong>{$total_games}</strong></p>";
    echo "<p>Wygrane: <strong>{$total_wins}</strong></p>";
    echo "<p>Przegrane: <strong>{$total_losses}</strong></p>";
    echo "</div>";
    
} catch (PDOException $e) {
    error_log("DB Error: {$e->getMessage()}");
    die('Błąd bazy danych');
}

$pdo = null;
?>

Whitelist vs Blacklist

Whitelist (ZALECANE):

// Lista dozwolonych wartości
$allowed_sort = ['level', 'coins', 'wins'];

if (in_array($_GET['sort'], $allowed_sort)) {
    $sort = $_GET['sort'];  // Bezpieczne
} else {
    $sort = 'level';  // Domyślna wartość
}

Blacklist (NIE ZALECANE):

// Próba blokowania "złych" wartości - NIGDY nie złapiesz wszystkich!
if ($_GET['sort'] != 'DROP' && $_GET['sort'] != 'DELETE') {
    $sort = $_GET['sort'];  // NADAL NIEBEZPIECZNE!
}

Najlepsze praktyki

ZAWSZE:

  1. Używaj prepared statements dla WSZYSTKICH zapytań z danymi użytkownika
  2. Używaj PDO dla nowych projektów (uniwersalność + bezpieczeństwo)
  3. Używaj htmlspecialchars() przy wyświetlaniu danych (ochrona XSS)
  4. Waliduj dane wejściowe (whitelist dla sortowania, LIMIT, itp.)
  5. Ustawiaj charset na utf8mb4 (obsługa emoji i polskich znaków)
  6. Loguj błędy do pliku, nie pokazuj szczegółów użytkownikowi
  7. Używaj number_format() dla czytelności dużych liczb
  8. Rzutuj na właściwy typ – (int) dla liczb, trim() dla stringów

NIGDY:

  1. Nie łącz zapytań SQL przez konkatenację z danymi użytkownika
  2. Nie pokazuj szczegółów błędów SQL użytkownikowi końcowemu
  3. Nie trzymaj danych dostępowych w kodzie (użyj config.php)
  4. Nie commituj config.php do repozytorium
  5. Nie używaj root bez hasła w środowisku produkcyjnym
  6. Nie ufaj danym z $_GET i $_POST – zawsze waliduj!