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>