W pracy programisty pełnego stosu bardzo satysfakcjonujące jest to (przynajmniej dla mnie), że jakiego zadania się człowiek nie podejmie, musi zawsze patrzeć na system z którym pracuje w szerszej, pełnej perspektywie. Jest to zadanie trudne i bardzo odpowiedzialne. Na co dzień pracuję tworząc nie tylko kod (core) aplikacji, ale także przygotowuję zapytania dla baz danych czy poprawiam UX aplikacji. I chociaż dla mnie wszystkie te elementy tworzą jeden spójny system, to muszę być przygotowany na to, że do kodu przysiądzie osoba, która będzie chciała skupić się wyłącznie na części systemu, a pozostałe będzie traktowała jako abstrakcję, o której nie musi wiedzieć nic oprócz tego, co dostarcza.
Dlatego ważne jest, aby już na samym początku przygotowywania rozwiązania dobrze odseparować od siebie warstwy logiczne. Dzisiaj chciałbym pokazać pomysł i jego prostą implementację, w jaki sposób można odseparować od siebie warstwę aplikacji od dostępu do bazy danych - na przykładzie prostego systemu ORM (ang. Object-relational mapping). Warto zaznaczyć, że korzystając z gotowych frameworków z reguły mamy już dostęp do tego typu narzędzi, dlatego nie ma sensu wynajdować koła na nowo i korzystać z gotowców. Jednak czasami warto spróbować napisać coś samodzielnie, aby przekonać się jak tego typu biblioteki działają i czy są trudne do zrealizowania samodzielnie :).
Na początek stwórzmy w bazie danych (na potrzeby tego wpisu użyję bazy MySQL) tabelę post. Następnie utwórzmy w niej pola: id CHAR(5), title VARCHAR(100), author VARCHAR(200), status INT. Do tabeli dodajmy kilka wpisów, np. zapytaniem:
INSERT INTO post (id, title, author, status) VALUES ('10000', 'Hello', 'Bartlomiej Romanek', 1);
INSERT INTO post (id, title, author, status) VALUES ('20000', 'World', 'Janusz Tracz', 2);
INSERT INTO post (id, title, author, status) VALUES ('30000', 'Friends', 'Bartłomiej Romanek', 2);
Utwórzmy także nowy projekt w naszym ulubionym IDE i dodajmy do niego katalog ORM, a w nim folder Model oraz Repository. W katalogu Model będziemy przechowywać encje (odzwzorowanie wiersza z bazy danych na obiekt w PHP) - otwórzmy więc tutaj nową klasę o nazwie Post, a w niej zdefiniujmy właściwości (pola z publicznym modyfikatorem dostępu) o nazwach tak jak w bazie danych:
<?php
class Post {
public $id;
public $title;
public $author;
public $status;
}
Następnie tworzymy repozytorium dla postów:
<?php
require 'ORM/Connection.php';
require 'ORM/Model/Post.php';
class PostRepository {
private $connection;
private $model;
public function __construct() {
$this->connection = new Connection('root', '', 'example', 'utf8');
$this->model = new Post();
}
public function get(string $id = ''): array {
if ($id === '') {
$posts = $this->connection->query("SELECT * FROM post", []);
} else {
$posts = $this->connection->query("SELECT * FROM post WHERE id = :id", [':id' => $id]);
}
$posts = $this->connection->query("SELECT * FROM post WHERE {$where}", [':id' => $id]);
$return = [];
foreach ($posts as $post) {
$i = 0;
$object = clone($this->model);
foreach ($object as &$field) {
if (isset($post[$i])) {
$field = $post[$i];
}
$i++;
}
$return[] = $object;
}
return $return;
}
}
Tak przygotowane repozytorium posiada tylko jedną metodę - get - która pozwala na pobieranie tablicy postów z bazy danych. To, jakie posty zostaną pobrane z bazy, zależy od id, które przekażemy do metody. Jeżeli nie przekażemy żadnego id, to zostaną zwrócone wszystkie elementy z tabeli. Powyższa metoda ma proste działanie – najpierw zostają pobrane z bazy rekordy, następnie w pętli foreach wypełniana jest instancja encji i zapisywana do tablicy, która na końcu zostaje zwrócona.
Na koniec dodajemy brakującą klasę Connection. Klasa ta jest wrapperem na klasę mysqli pozwalająca na połączenie z bazą danych mysql:
<?php
class Connection {
private static $db;
public function __construct($login, $password, $name, $charset) {
if(self::$db === null) {
self::$db = new \mysqli('localhost', $login,
$password, $name);
if (!self::$db->connect_error) {
self::$db->set_charset($charset);
}
}
}
public function query(string $query, array $params = []): array {
foreach ($params as $param => $value) {
$query = str_ireplace($param, '?', $query);
}
$statement = self::$db->prepare($query);
foreach ($params as $param => $value) {
switch (true) {
case is_int($value):
$type = 'i';
break;
case is_double($value):
$type = 'd';
break;
case is_string($value):
default:
$type = 's';
break;
}
$statement->bind_param($type, $value);
}
$statement->execute();
$result = $statement->get_result();
$result = $result->fetch_all(MYSQLI_NUM);
return $result;
}
}
Powyższa metoda zakłada, że zapytanie do bindowania ma składnię PDO (a więc znaki zapytania w miejscach, gdzie znajdują się zmienne). Dlatego też występuje w metodzie zamiana parametrów na znaki zapytania.
I to już w sumie wszystko! Możemy teraz utworzyć w głównym katalogu plik index.php, w nim wykorzystać nasze repozytorium:
<?php
require 'ORM/Repository/PostRepository.php';
$postRepository = new PostRepository();
$posts = $postRepository->get('10000');
$post = $posts[0];
echo $post->author;
Wynikiem jest napis:
Bartlomiej Romanek
Oczywiście powyższy przykład jest uproszczony do bólu ;). Ale mam nadzieję, że pokazał ideę. Powyższe pliki źródłowe w całości możesz pobrać stąd.
Komentarze
Ten wpis nie posiada komentarzy.
Dodaj komentarz
Pola oznaczone gwiazdką (*) są wymagane. Komentarze są wstępnie moderowane i mogą nie pojawić się na stronie.