[PHP] (Skrypty) – Własny CMS? cz. 2 – Baza danych

blog wpis Danielek cz2 zdj. 3

[PHP] (Skrypty) – Własny CMS? cz. 2 – Baza danych

 

CMS – zarządzanie bazą danych

W poprzedniej części napisaliśmy jeden z podstawowych składników CMS’a, który służy do zarządzania zmiennymi/danymi strony oraz drugi do zarządzania modułami.

Wg. poprzedniego założenia w tej części miała być obsługa znaczników oraz miały powstać moduły itp.Jednakże postanowiłem, że Was jeszcze pomęczę czymś zawiłym 😉 Czymś bez czego nie było by CMS’a.Zarządzanie bazą danych to jedna z najcięższych rzeczy, którą chcielibyśmy robić. Można (a nawet polecam) do tego użyć już gotowe rozwiązania, takie jak np. Doctrine. Jednakże w celach ‚naukowych’ stworzymy sobie coś mniejszego, ale spełniającego swoje zadanie. To wszystko po to, aby zobaczyć jak to działa ‚mnie więcej’ od środka.

Oczywiście zaznaczam, że jest to wersja tzw. ‚lite’, czyli trochę odchudzona.W komentarzach zobaczymy jakie wymyślicie usprawnienia :)Na koniec całego CMS’a napiszę i opiszę co i jak powinno być dodane i zrobione.

1. Baza danych – co i jak

Każdy na początku wie, jakie jest ciężkie zarządzanie bazą. Ciągłe używanie tych samych komend – powtarzanie się w prostych rzeczach. Zamiast na obiektach danych operujemy bezpośrednio na bazie. Tak się robi z początku naszej kariery 😉

Natomiast tutaj zrobimy coś na zasadzie object-relational mapper – ORM. Ukryjemy całą mechanikę odwołań do bazy i będziemy operować na modelach naszych obiektów. Będzie to wyglądać mniej więcej tak:

// nasz model wiadomości

class NewsData extends CDataBaseHelper {

public $title = „VARCHAR(32)”;

public $text = „TEXT”;

}

 

// pobranie danych z bazy po ID=12

$article = (new NewsData)->getData(12);

echo ‚title: ‚.$article->title.’ # text: ‚.$article->text.'<br />’;

 

// zmiana danych w bazie

$article->title = ‚Dobreprogramy.pl’;

$article->save();

 

// szukanie – zwraca tablice obiektów NewsData zawierających w tytule ‚obre’

$news = (new NewsData)->filter(„id>0 AND title LIKE ‚%obre%'”);

foreach($news as $article)

echo ‚title: ‚.$article->title.’ # text: ‚.$article->text.'<br />’;

Czy nie jest to prostsze niż bawienie się ciągle z kodem bazodanowym? Tak się to mnie więcej robi w wielu frameworkach, takich jak RoR, Django, cakePHP, Symfony.Ma to swoje zalety, ale też wady. Jedną z największych wad może być wydajność. A jeśli idzie to coraz bardziej jak np. w RoR, to mamy mniejszą kontrolę nad bazą. Jak to się mówi: „coś za coś”. Nie jest tak źle. Do większości zadań narzut nie jest taki duży. Do bardziej zaawansowanych używa się już inne rzeczy. Aczkolwiek większość wygląda tak samo.

Aby to wyżej nam działało musimy wpierw stworzyć klasę, z której będziemy mieć dostęp w każdym miejscu do bazy. Do tego posłuży nam kolejny singleton: CDataBase. Nasza baza danych to SQLite, ale nie ma przeszkód aby zaimplementować dodatkowe, jak np. MySQL. Do przeglądania i zarządzania bazą SQLite polecam aplikację: SQLite Data Browser ze względu, iż jest obsługiwany na każdym (prawie) systemie. Dla użytkowników Windows jest ‚kombajn’Database Browser, który obsługuje nie tylko SQLite. Jest też najbardziej oczywista wersja obsługi SQLite – z linii komend (np. w Windows jest to sqlite3.exe). Ale wybór zostawiam Wam 🙂

blog wpis Danielek cz2 zdj. 1                                                                                                                 Singleton do obsługi bazy danych

 

A tak wygląda kod CDataBase do obsługi bazy:

class CDataBase {

static private $dbHandler = null;

static private $szDBName = ‚db/website_db.sqlite’;

 

static public function init($szDBName = ‚db/website_db.sqlite’) {

self::$szDBName = $szDBName;

}

 

static public function getHandler() {

if(self::$dbHandler === null) {

try {

self::$dbHandler = new PDO(‚sqlite:’.self::$szDBName);

}

catch(PDOException $e) {

echo $e-getMessage();

}

}

return self::$dbHandler;

}

 

private function __construct() {}

private function __destruct() {}

}

Mamy tutaj tylko 2 metody, gdzie init() ustawia nazwę i miejsce naszej bazy. Nie trzeba używać, ponieważ ustawiliśmy już jako prywatną zmienną należącą do klasy. Druga metoda, to getHandler(), która łączy się z bazą i pobiera do niej tzw. uchwyt, na którym będziemy operować dalej. Metoda ta wpierw sprawdza, czy już wcześniej nie uzyskiwaliśmy dostępu do bazy i jeśli tak, to pobiera już wcześniej zdefiniowany uchwyt. Zapobiegamy niezbędnym narzutom wywołań.

Teraz przyjdzie kolej na największego ‚męczennika’, dzięki któremu będziemy mogli posługiwać się obiektami danych w odwołaniu do bazy, bez jawnego udziału tejże bazy – taki nasz mały ORM. Klasa ta CDataBaseHelper() będzie klasą abstrakcyjną, którą będziemy dziedziczyć w naszych modelach danych. Będzie ona pobierać i mapować automatycznie dane/zmienne naszych obiektów do/z bazy.

Każdy obiekt w bazie posiada jeden unikalny wpis. Zazwyczaj jest to ID obiektu, które jest automatycznie inkrementowany (zwiększany). Jest to jeden element, który jest wspólny dla wszystkich danych. Dlatego też będzie to zmienna właśnie naszej klasy bazowej dziedziczona do obiektów wyżej. Dzięki temu nie będziemy musieli jej deklarować w naszych modelach i będzie nam łatwiej napisać naszego ORM’a.blog wpis Danielek cz2 zdj. 2

Nasz mały ORM 😉

blog wpis Danielek cz2 zdj. 3

Nasz mały ORM 😉 cd.

Mam nadzieję, że się nie przestraszyliście kodu z obrazków ;). Może i wygląda na zagmatwany, ale taki nie jest :>

Teraz kod CDataBaseHelper a zaraz po nim opiszę i wytłumaczę, jak to się je :] W niektórych miejscach przeniosłem część składni do nowej linii dla widoczności tutaj na blogu – oczywiście kod jest nadal prawidłowy.

abstract class CDataBaseHelper {

public $id = „INTEGER PRIMARY KEY”;

 

public function getData($id) {

$hResult = CDataBase::getHandler()->query(

‚SELECT * FROM ‚.$this->_getClassName().’ WHERE id=’.$id

);

if(!$hResult) return false;

 

$row = $hResult->fetch(PDO::FETCH_OBJ);

if($row) {

foreach($this->_getClassVars() as $key => $value) {

$this->{$key} = $row->{$key};

}

}

return $this;

}

 

public function filter($szFilter) {

$hResult = CDataBase::getHandler()->query(

‚SELECT * FROM ‚.$this->_getClassName().’ WHERE ‚.$szFilter

);

if(!$hResult) return false;

 

$rows = $hResult->fetchAll(PDO::FETCH_OBJ);

if($rows){

$aData = array();

foreach($rows as $row) {

$obj = new $this;

foreach($this->_getClassVars() as $key => $value)

$obj->{$key} = $row->{$key};

$aData[] = $obj;

}

return $aData;

}

return false;

}

 

public function save() {

$hResult = CDataBase::getHandler()->query(

‚SELECT * FROM ‚.$this->_getClassName().’ WHERE id=’.$this->id

);

if($hResult === false) {

$args = array();

foreach($this->_getClassVars() as $key => $value) {

if($key === ‚id’) {

$args[] = ‚NULL’;

continue;

}else {

$args[] = „‚{$this->{$key}}'”;

}

}

 

$szInsertSQL = ‚INSERT INTO ‚.$this->_getClassName()

.’ VALUES(‚.implode(‚,’,$args).’)’;

CDataBase::getHandler()->exec($szInsertSQL);

} else {

$args = array();

foreach($this->_getClassVars() as $key => $value) {

if($key === ‚id’) continue;

$args[] = $key.’=’.”‚{$this->{$key}}'”;;

}

$szUpdateSQL = ‚UPDATE ‚.$this->_getClassName()

.’ SET ‚.implode(‚,’, $args).’ WHERE id=’.$this->id;

CDataBase::getHandler()->exec($szUpdateSQL);

}

}

 

public function delete($id = -1) {

if($id === -1)

CDataBase::getHandler()->exec(‚DELETE FROM ‚.$this->_getClassName().’ WHERE id=’.$this->id);

else

CDataBase::getHandler()->exec(‚DELETE FROM ‚.$this->_getClassName().’ WHERE id=’.$id);

}

 

public function createTable() {

$aSQL = array();

foreach($this->_getClassVars() as $key => $value)

$aSQL[] = $key.’ ‚.$value;

$szSQL = implode(‚,’, $aSQL);

$createSQL = ‚CREATE TABLE ‚.$this->_getClassName().’ (‚.$szSQL.’)’;

$hResult = CDataBase::getHandler()->exec($createSQL);

if(!$hResult) return false;

 

return true;

}

 

protected function _getClassName() { return get_class($this); }

protected function _getClassVars() { return get_class_vars($this->_getClassName()); }

}

 

Jest tutaj 6 metod oraz 1 zmienna:

public $id = „INTEGER PRIMARY KEY”;
  • indeks w bazie danych
protected function _getClassName()
  • metoda, dzięki której będziemy pobierać nazwę klasy naszego modelu, umożliwiającą mapowanie do/z bazy
protected function _getClassVars()
  • pobiera wszystkie zmienne klasy również w celu mapowania do/z bazy

Dzięki tym dwu powyżej metodom będzie się działa nasza mini magia 😉 Są to bardzo ważne metody w tej klasie, które będziemy używać w jej całym obrębie. Tutaj widać możliwości programowania obiektowego, a dokładniej dziedziczenie. Praktycznie wszystko dzieje się w klasie/obiekcie dziedziczonym, który pobiera dane z klasy/obiektu który dziedziczy.

public function createTable()
  • używamy tylko raz w celu stworzenia tabeli w bazie. Najpierw pobieramy nazwy zmiennych i ich wartości, które są opisem danych w bazie do tablicy. Następnie tablicę zamieniamy w napis oddzielając każdy element przecinkiem, który następnie podajemy jako składnia SQL. Wcześniej jednak jeszcze pobieramy nazwę klasy, która będzie nazwą tabeli z naszymi danymi w bazie.
public function save()
  • Zapisuje zmiany w bazie. Metoda ta sprawdza wpierw, czy obiekt o podanym ID istnieje. Jeśli nie ma takiego, to wstawia jako nowy. W innym przypadki istniejący aktualizuje. W pierwszym przypadku wykorzystujemy to, że kiedy PDO::query ‚obleje’ komunikacje zwraca fałsz. Wtedy pobieramy listę zmiennych obiektu do tablicy, sprawdzając przy okazji, czy nie jest to $idi jeśli tak to ustawiamy na NULL, co podpowie bazie aby automatycznie zwiększyć licznik (ID). Listę zmiennych obiektu pobieramy za pomocą wcześniej pobranej tablicy zmiennych klasy.
  • Tutaj trzeba zauważyć, jak jest żonglowanie pomiędzy klasą a obiektem. Nie pobieramy wartości zmiennej klasy a obiektu. Zauważcie kod
$this->{$key}

// a dokładnie

$args[] = „‚{$this->{$key}}'”;

  • który może być wielu osobom nieznajomy. Umożliwia wyłuskanie nazwy zmiennej i wykorzystanie jej do odwołania się do odpowiedniej zmiennej w obiekcie.
  • W dalszej części dzieje się to samo, tyle że dane są aktualizowane w bazie.
public function filter($szFilter)
  • Szukamy / pobieramy tablicę obiektów z bazy spełniającą warunek $szFilter. Jako parametr przekazujemy warunek WHEREskładni SQL, np.:
$news = (new NewsData)->filter(„id>0 AND title=’dobreprogramy'”);

// albo – chociaż ta wyżej bardziej polecana

$nd = new NewsData;

$news = $nd->filter(„id>0 AND title=’%obr%'”);

public function get($id)
  • Pobiera dane z bazy na podstawie ID.
public function delete($id=-1)
  • Kasuje rekord z bazy. Jeśli parametr nie jest podany, to usuwany jest aktualny obiekt z bazy.

Jakby ktoś czegoś nie zrozumiał, to można pytać w komentarzach. Postaram się w miarę czasu odpowiedzieć…

Największa praca za nami. Teraz wystarczy już zadeklarować nasze modele/klasy do obiektów, których będziemy używać na naszej stronie. Modele te będą również raz stworzone w bazie. Trochę się pomęczyliśmy, ale to wszystko po to, aby od tej pory było wszystko prostsze i wygodniejsze.

 

blog wpis Danielek cz2 zdj. 4                                                                                                                 Przykładowe Obiekty

 

Jedyne co modele muszą, to:

  • dziedziczyćCDataBaseHelper
  • posiadać co najmniej jedną zmienną
  • każda zmienna musi mieć wartość odpowiadającą typowi w bazie danych
class NewsData extends CDataBaseHelper {

public $title = „VARCHAR(32)”;

public $text = „TEXT”;

}

 

class SiteSettingsData extends CDataBaseHelper {

public $title = „VARCHAR(32)”;

public $keywords = „VARCHAR(32)”;

public $description = „VARCHAR(32)”;

public $author = „VARCHAR(32)”;

}

 

 

(new NewsData)->createTable();

(new SiteSettingsData)->createTable();

A jak tego używać?

  • wpierw tworzymy modele, np. jak wyżej
  • następnie tworzymy tabele w bazie, np. tak jak wyżej:
(new NewsData)->createTable();
  • dodawanie danych
$article = new NewsData;

$article->title = ‚Dobreprogramy’;

$article->text = ‚Na Dobreprogramy.pl tworzymy swój CMS ;)’;

$article->save()

  • pobieranie danych
$article = (new NewsData)->getData(1);

echo ‚title: ‚. $article->title.’; text: ‚.$article->text;

  • wyszukiwanie / pobieranie danych wg. podanych zakresów
// pobierana jest tablica obiektów

$news = (new NewsData)->filter(„title LIKE ‚%obre%'”);

foreach($news as $article)

echo ‚title: ‚. $article->title.’; text: ‚.$article->text.'<br />’;

  • aktualizowanie danych
$article = (new NewsData)->getData(1);

$article->title = ‚Dobreprogramy.pl’;

$article->text = ‚Na DP.pl tworzymy swój CMS ;)’;

$article->save()

  • usuwanie danych
$article = (new NewsData)->getData(1);

$article->delete();

// albo

(new NewsData)->delete(1);

Co teraz?

Jest to wersja tzw. lite, ale spełnia swoje zadanie. Można ulepszyć, dodać parę rzeczy. Komunikaty o błędach – logowanie można również dodać. Można dodać sprawdzanie poprawności danych. Użycie PDO::prepare tekże może okazać się pomocne, przy czym dodajemy wtedy sprawdzanie danych w modelach i na ich zasadzie przekazywanie parametru typu przy PDO:bind.

Oczywiście do modelów/klas/obiektów można przypisywać normalnie metody, którymi będziemy się posługiwać. To są po prostu normalne obiekty + trochę magii. Tylko trzeba pamiętać, że w takim obiekcie każda zmienna obiektowa ma odwzorowanie w bazie.

Mógłbym to wszystko tutaj umieścić, ale był by to wtedy bardzo długi wykład. A nie o to chodziło 🙂 A inna sprawa, te ‚wszystko’, jest naprawdę bardzo dużo…

Jeśli coś nie jest zrozumiałe, to proszę śmiało pytać w komentarzach. Postaram się odpowiedzieć w miarę przystępnym czasie.

 

 

Cały Artykuł jest autorstwa jednego z naszych programistów, jest on również dostępny na stronie:

http://www.dobreprogramy.pl/d4kw0x

 

No Comments

Post a comment