Scroll
1.5. Povezivanje klasa za rad sa bazom podataka i šablonima
Kreirali smo strukturu za naš framework, sada je vreme da razmislimo o čuvanju podataka: vesti, proizvoda. Objekat za rad sa bazom podataka treba da može:
- Upravljati konekcijama sa bazom
- Obezbediti malu apstrakciju od baze podataka
- Keširati upite
- Pojednostaviti uobičajene operacije sa bazom
Za to ćemo napraviti objekat Registry/objects/db.class.php:
<?php /** * Upravljanje bazom podataka * Pruža malu apstrakciju baze podataka */ class db { /** * Omogućava višestruke konekcije sa bazom * retko se koristi ali može biti korisno */ private $connections = array(); /** * Aktivna konekcija * setActiveConnection($id) menja aktivnu konekciju */ private $activeConnection = 0; /** * Keširani izvršeni upiti */ private $queryCache = array(); /** * Keširani podaci koji su dobijeni */ private $dataCache = array(); /** * Poslednji upit */ private $last; /** * Konstruktor */ public function __construct() { } /** * Kreira novu konekciju * @param String hostname baze * @param String korisničko ime baze * @param String lozinka baze * @param String ime baze * @return int ID nove konekcije */ public function newConnection( $host, $user, $password, $database ) { $this->connections[] = new mysqli( $host, $user, $password, $database ); $connection_id = count( $this->connections )-1; if( mysqli_connect_errno() ) { trigger_error('Greška pri povezivanju na host. '.$this->connections[$connection_id]->error, E_USER_ERROR); } return $connection_id; } /** * Zatvara aktivnu konekciju */ public function closeConnection() { $this->connections[$this->activeConnection]->close(); } /** * Menja aktivnu konekciju * @param int ID nove konekcije */ public function setActiveConnection( int $new ) { $this->activeConnection = $new; } /** * Kešira upit * @param String upit * @return int pokazivač na keširani upit */ public function cacheQuery( $queryStr ) { if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) ) { trigger_error('Greška pri izvršavanju i keširanju upita: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR); return -1; } else { $this->queryCache[] = $result; return count($this->queryCache)-1; } } /** * Vraća broj redova iz keša * @param int pokazivač keša upita * @return int broj redova */ public function numRowsFromCache( $cache_id ) { return $this->queryCache[$cache_id]->num_rows; } /** * Vraća rezultate iz keša * @param int pokazivač keša upita * @return array redovi rezultata */ public function resultsFromCache( $cache_id ) { return $this->queryCache[$cache_id]->fetch_array(MYSQLI_ASSOC); } /** * Kešira podatke * @param array podaci * @return int pokazivač na keširane podatke */ public function cacheData( $data ) { $this->dataCache[] = $data; return count( $this->dataCache )-1; } /** * Vraća podatke iz keša * @param int pokazivač keša podataka * @return array podaci */ public function dataFromCache( $cache_id ) { return $this->dataCache[$cache_id]; } /** * Briše zapise iz tabele * @param String ime tabele * @param String uslov za brisanje * @param int limit brisanih redova */ public function deleteRecords( $table, $condition, $limit ) { $limit = ( $limit == '' ) ? '' : ' LIMIT ' . $limit; $delete = "DELETE FROM {$table} WHERE {$condition} {$limit}"; $this->executeQuery( $delete ); } /** * Ažurira zapise u tabeli * @param String ime tabele * @param array izmene polje => vrednost * @param String uslov * @return bool */ public function updateRecords( $table, $changes, $condition ) { $update = "UPDATE " . $table . " SET "; foreach( $changes as $field => $value ) { $update .= "`" . $field . "`='{$value}',"; } $update = substr($update, 0, -1); if( $condition != '' ) { $update .= " WHERE " . $condition; } $this->executeQuery( $update ); return true; } /** * Ubacuje zapis u tabelu * @param String ime tabele * @param array podaci polje => vrednost * @return bool */ public function insertRecords( $table, $data ) { $fields = ""; $values = ""; foreach ($data as $f => $v) { $fields .= "`$f`,"; $values .= ( is_numeric( $v ) && ( intval( $v ) == $v ) ) ? $v."," : "'$v',"; } $fields = substr($fields, 0, -1); $values = substr($values, 0, -1); $insert = "INSERT INTO $table ({$fields}) VALUES({$values})"; $this->executeQuery( $insert ); return true; } /** * Izvršava upit * @param String upit */ public function executeQuery( $queryStr ) { if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) ) { trigger_error('Greška pri izvršavanju upita: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR); } else { $this->last = $result; } } /** * Vraća redove poslednjeg upita * @return array */ public function getRows() { return $this->last->fetch_array(MYSQLI_ASSOC); } /** * Broj pogođenih redova poslednjim upitom * @return int */ public function affectedRows() { return $this->connections[$this->activeConnection]->affected_rows; } /** * Sanitizuje podatke * @param String podaci za sanitizaciju * @return String očišćeni podaci */ public function sanitizeData( $data ) { return $this->connections[$this->activeConnection]->real_escape_string( $data ); } /** * Destruktor, zatvara konekcije */ public function __destruct() { foreach( $this->connections as $connection ) { $connection->close(); } } } ?>
Pre nego što krenemo na konekciju sa bazom, pogledajmo šta naš klasa radi. Možemo raditi jednostavne operacije ubacivanja, ažuriranja, brisanja preko metoda klase:
// Ubacivanje $registry->getObject('db')->insertRecords( 'products', array('name'=>'Šolja' ) ); // Ažuriranje $registry->getObject('db')->updateRecords( 'products', array('name'=>'Crvena šolja' ), 'ID=2' ); // Brisanje $registry->getObject('db')->deleteRecords( 'products', "name='Crvena šolja'", 5 );
Klasa takođe podržava keširanje.
Sada ćemo dodati još jedan objekat za upravljanje šablonima Registry/objects/template.class.php
<?php if ( ! defined( 'FW' ) ) { echo 'Ovaj fajl se može pozvati samo iz index.php, ne direktno'; exit(); } /** * Klasa za rad sa šablonima */ class template { private $page; /** * Konstruktor */ public function __construct() { include( APP_PATH . '/Registry/objects/page.class.php'); $this->page = new Page(); } /** * Dodaje šablon u stranicu * @param String $tag oznaka u sadržaju, npr. {hello} * @param String $bit putanja do šablona */ public function addTemplateBit( $tag, $bit ) { if( strpos( $bit, 'Views/' ) === false ) { $bit = 'Views/Templates/' . $bit; } $this->page->addTemplateBit( $tag, $bit ); } /** * Uklanja i zamenjuje oznake šablona u sadržaju */ private function replaceBits() { $bits = $this->page->getBits(); foreach( $bits as $tag => $template ) { $templateContent = file_get_contents( $template ); $newContent = str_replace( '{' . $tag . '}', $templateContent, $this->page->getContent() ); $this->page->setContent( $newContent ); } } /** * Zamenjuje oznake u sadržaju sa podacima ili kešom */ private function replaceTags() { $tags = $this->page->getTags(); foreach( $tags as $tag => $data ) { if( is_array( $data ) ) { if( $data[0] == 'SQL' ) { $this->replaceDBTags( $tag, $data[1] ); } elseif( $data[0] == 'DATA' ) { $this->replaceDataTags( $tag, $data[1] ); } } else { $newContent = str_replace( '{' . $tag . '}', $data, $this->page->getContent() ); $this->page->setContent( $newContent ); } } } /** * Zamenjuje oznake podacima iz baze */ private function replaceDBTags( $tag, $cacheId ) { $block = ''; $blockOld = $this->page->getBlock( $tag ); while ($tags = Registry::getObject('db')->resultsFromCache( $cacheId ) ) { $blockNew = $blockOld; foreach ($tags as $ntag => $data) { $blockNew = str_replace("{" . $ntag . "}", $data, $blockNew); } $block .= $blockNew; } $pageContent = $this->page->getContent(); $newContent = str_replace( '' . $blockOld . '', $block, $pageContent ); $this->page->setContent( $newContent ); } /** * Zamenjuje oznake podacima iz keša */ private function replaceDataTags( $tag, $cacheId ) { $block = $this->page->getBlock( $tag ); $blockOld = $block; while ($tags = Registry::getObject('db')->dataFromCache( $cacheId ) ) { foreach ($tags as $tag => $data) { $blockNew = $blockOld; $blockNew = str_replace("{" . $tag . "}", $data, $blockNew); } $block .= $blockNew; } $pageContent = $this->page->getContent(); $newContent = str_replace( $blockOld, $block, $pageContent ); $this->page->setContent( $newContent ); } /** * Vraća objekat stranice */ public function getPage() { return $this->page; } /** * Gradi sadržaj stranice iz šablona */ public function buildFromTemplates() { $bits = func_get_args(); $content = ""; foreach( $bits as $bit ) { if( strpos( $bit, 'skins/' ) === false ) { $bit = 'Views/Templates/' . $bit; } if( file_exists( $bit ) == true ) { $content .= file_get_contents( $bit ); } } $this->page->setContent( $content ); } /** * Pretvara niz podataka u oznake */ public function dataToTags( $data, $prefix ) { foreach( $data as $key => $content ) { $this->page->addTag( $key.$prefix, $content); } } public function parseTitle() { $newContent = str_replace('', '<title>'. $this->page->getTitle(), $this->page->getContent() ); $this->page->setContent( $newContent ); } /** * Parzira i prikazuje sadržaj */ public function parseOutput() { $this->replaceBits(); $this->replaceTags(); $this->parseTitle(); } } ?> </pre> <p>Definisali smo i klasu Page Registry/objects/page.class.php:</p> <pre class="1;"> <?php /** * Naša klasa za stranicu * Omogućava dodatne funkcije kao što su zaštićene stranice, dodavanje js/css itd. */ class page { private $css = array(); private $js = array(); private $bodyTag = ''; private $bodyTagInsert = ''; private $authorised = true; private $password = ''; private $title = ''; private $tags = array(); private $postParseTags = array(); private $bits = array(); private $content = ""; function __construct() { } public function getTitle() { return $this->title; } public function setPassword( $password ) { $this->password = $password; } public function setTitle( $title ) { $this->title = $title; } public function setContent( $content ) { $this->content = $content; } public function addTag( $key, $data ) { $this->tags[$key] = $data; } public function getTags() { return $this->tags; } public function addPPTag( $key, $data ) { $this->postParseTags[$key] = $data; } public function getPPTags() { return $this->postParseTags; } public function addTemplateBit( $tag, $bit ) { $this->bits[ $tag ] = $bit; } public function getBits() { return $this->bits; } public function getBlock( $tag ) { preg_match ('#<!-- START '. $tag . ' -->(.+?)<!-- END '. $tag . ' -->#si', $this->content, $tor); $tor = str_replace ('<!-- START '. $tag . ' -->', "", $tor[0]); $tor = str_replace ('<!-- END ' . $tag . ' -->', "", $tor); return $tor; } public function getContent() { return $this->content; } } ?> </pre> <p>Sada kada smo kreirali klase za rad sa bazom i šablonima, povežimo ih.</p> <p>Dodajmo metodu <code>storeCoreObjects()</code> u Registry/registry.class.php:</p> <pre class="1;"> public function storeCoreObjects() { $this->storeObject('db', 'db' ); $this->storeObject('template', 'template' ); } </pre> <p>U njoj ćemo definisati koje klase povezujemo.</p> <p>Takođe napravimo tabelu users sa poljima id, name, email. SQL fajl sa primerom baze biće dodat na GitHub.</p> <p>Sada kreirajmo šablon Views/Templates/main.tpl.php za prikaz glavne stranice:</p> <pre class="1;"> <html> <head> <title>Powered by PCA Framework</title> </head> <body> <h1>Naši korisnici</h1> <p>Ispod je spisak naših korisnika:</p> <ul> <!-- START users --> <li>{name} {email}</li> <!-- END users --> </ul> </body> </html> </pre> <p>U index.php povežimo bazu i šablon:</p> <pre class="1;"> <?php session_start(); error_reporting(E_ALL); define( "APP_PATH", dirname( __FILE__ ) ."/" ); define( "FW", true ); function __autoload( $class_name ) { require_once('Controllers/' . $class_name . '/' . $class_name . '.php' ); } require_once('Registry/registry.class.php'); $registry = Registry::singleton(); $registry->storeCoreObjects(); $registry->getObject('db')->newConnection('localhost', 'root', '', 'framework'); $registry->getObject('template')->buildFromTemplates('main.tpl.php'); $cache = $registry->getObject('db')->cacheQuery('SELECT * FROM users'); $registry->getObject('template')->getPage()->addTag('users', array('SQL', $cache) ); $registry->getObject('template')->getPage()->setTitle('Naši korisnici'); $registry->getObject('template')->parseOutput(); print $registry->getObject('template')->getPage()->getContent(); print $registry->getFrameworkName(); exit(); ?> </pre> <p>Ako je sve ispravno i baza ima korisnike, biće prikazan spisak korisnika.</p> <p>U slučaju grešaka proverite kod na GitHubu jer je moguće da su neke ispravke u prethodnim člancima potrebne.</p>