Obiektowe Połączenie z Bazą (Singleton)
Zadanie 100: Obiektowe połączenie z bazą danych (Singleton)
Wstęp
Programowanie obiektowe (OOP) to paradygmat, który pozwala nam na tworzenie czytelniejszego, modularnego i łatwego w rozbudowie kodu. Jednym z popularnych (choć należy stosować go z rozwagą) wzorców projektowych jest Singleton. Wyobraź sobie, że w swojej aplikacji w wielu miejscach musisz pobierać dane z bazy. Tworzenie nowego połączenia dla każdego zapytania jest bardzo nieoptymalne. Wzorzec Singleton gwarantuje nam, że w danym momencie istnieje tylko jedna instancja danej klasy, co idealnie sprawdza się w przypadku obiektu reprezentującego otwarte połączenie z bazą danych!
Cel zadania
Twoim zadaniem jest stworzenie klasy Database napisanej w PHP, która będzie łączyła się z MySQL wykorzystując wzorzec projektowy Singleton. Klasa ta powinna posiadać metody pozwalające m.in. na wysłanie zapytania (query()), odebranie wyników w postaci tablicy asocjacyjnej (fetch()) i wreszcie poprawne zakończenie połączenia (close()).
Wymagania techniczne
- Programowanie obiektowe (OOP) w PHP.
- Klasa implementująca wzorzec Singleton (prywatny konstruktor zapobiegający powoływaniu instancji słowem
new, prywatna statyczna zmienna przechowująca instancję, publiczna metoda statycznagetInstance()). - Wykorzystanie biblioteki PDO (lub mysqli) do komunikacji z bazą MySQL.
Kroki do wykonania
1. Podstawowy szkielet Singletona
Utwórz plik Database.php. Zbuduj w nim ogólny szkielet klasy. Pamiętaj, aby składowe i funkcje kluczowe dla wzorca były statyczne. Konstruktor zaś - prywatny.
<?php
class Database {
private static $instance = null;
private $connection;
// Prywatny konstruktor zapobiega bezpośredniemu tworzeniu obiektów przez 'new'
private function __construct() {
// Tu zrealizujemy połączenie
}
// Metoda statyczna gwarantująca istnienie tylko jednej instancji
public static function getInstance() {
if (self::$instance == null) {
self::$instance = new Database();
}
return self::$instance;
}
}
?>
[!IMPORTANT] Commit 1: Utworzenie struktury klasy Database oraz patternu Singleton.
2. Logika połączenia z bazą
Uzupełnij prywatny konstruktor o połączenie z bazą PDO.
Pamiętaj o wychwytywaniu błędów używając bloku try...catch. Załóż nową bazę lub przygotuj sobie w niej przykładową tabelę products (ID, name, price).
// Przykład uzupełnionego konstruktora z PDO
private function __construct() {
$host = 'localhost';
$db = 'baza_testowa'; // Zmień na własną
$user = 'root';
$pass = '';
try {
$this->connection = new PDO("mysql:host=$host;dbname=$db;charset=utf8", $user, $pass);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
die("Błąd połączenia z bazą danych: " . $e->getMessage());
}
}
[!IMPORTANT] Commit 2: Ustanowienie logicznego łączenia z MySQL w strukturze OOP.
3. Metody zapytania (query) i pobierania (fetch)
Dodaj dwie metody do klasy Database:
query($sql): Wysyła podane w argumencie zapytanie SQL do bazy.fetch($stmt): Zwraca pobrane za pomocą zapytania wyniki w postaci tablicy asocjacyjnej.
public function query($sql) {
// Wykonujemy zapytanie PDO i zwracamy tzw. PDOStatement
$stmt = $this->connection->query($sql);
return $stmt;
}
public function fetch($stmt) {
// Funkcja pobiera wszystkie uzyskane wcześniej przy query() wiersze jako tablice asocjacyjne
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
[!IMPORTANT] Commit 3: Implementacja metod do manipulacji i czytania danych z tabel bazy danych.
4. Metoda close()
Dodaj również metodę close() służącą do formalnego zakończenia komunikacji. W przypadku interfejsu PDO zwalniamy zasoby bazy poprzez nadanie wartości null powołanej instancji nawiązanego złącza.
public function close() {
$this->connection = null;
}
[!IMPORTANT] Commit 4: Dodano procedurę close(), która przerywa pracę aktywnego złącza PDO.
5. Praktyczny przykład użycia – plik index.php
Wykorzystaj stworzoną klasę w praktyce w pliku głównym. Przekonajmy się, czy to w ogóle działa!
<?php
require_once 'Database.php';
// Pobieramy naszą powołaną jedyną instancję bazy danych
$db = Database::getInstance();
// Wysyłamy nasze zapytanie korzystając ze stworzonej przez nas metody
$result = $db->query("SELECT * FROM products");
// Pobieramy dane zapytania zapisane w formie standardowej tablicy (array)
$dane = $db->fetch($result);
// Prezentacja
echo "<pre>";
print_r($dane);
echo "</pre>";
// Na koniec – poprawne pozbycie się połączenia
$db->close();
?>
[!IMPORTANT] Commit 5: Praktyczne wywołanie Singletona oraz zapytania PDO w osobnym pliku index.php.
🔥 Dodatkowe zadania (dla ambitnych i rządnych wiedzy)
- Bezpieczeństwo (Prepared Statements): Zmodernizuj metodę
queryw taki sposób, aby przyjmowała dwa argumenty: pierwszy to zapytanie SQL ze znakami zapytania np.SELECT * FROM products WHERE id=?, a drugi to opcjonalna tablica parametrów do bindowania za pomocą pętli bezpieczeństwaexecute($tablica). Ochroni to Twoją bazę m.in. przed atakami SQL Injection. - Helper typu insert(): Stwórz uniwersalną metodę
insert($tabela, $dane), dzięki której za pomocą jednego wywołania wkleisz rekordy do bazy stosując tablicę asocjacyjną np.['nazwa_pola' => 'wartosc']. - Złamanie reguł (klonowanie): Co jeśli programista w
index.phpnapisze$nowa_baza = clone $db;? W domyślnej formie Singleton użyty wyżej na to pozwala! Dodaj w klasie prywatną metodę magicznąprivate function __clone() {}i sprawdź, czy wciąż można w sposób niedozwolony sklonować instancję Twojej Database. - Złamanie reguł (unserialize): Analogicznie do klonowania, dodaj prywatną metodę magiczną
private function __wakeup() {}, aby zabronić deserializacji z zewnętrznego źródła.
Git Help - Jak commitować?
Gdy zrobisz dany krok przedstawiony wyżej w liście zadań, pamiętaj o tworzeniu komitów (zapisywanie punktów Twojej pracy).
Otwórz terminal wewnątrz folderu z zadaniem (w VSCode możesz to zrobić skrótem Ctrl + `) i wprowadź polecenie z nazwą danego etapu:
git add .
git commit -m "Commit 1 - Tworzymy strukturę klasy Singleton Database"
Jeżeli git wyrzuci błąd z uwierzytelnieniem (First-time user setup problem), po prostu ustaw usera w swojej konsoli:
git config --global user.name "Swoje Imię"
git config --global user.email "[email protected]"