<?xml version="1.0" encoding="utf-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
<channel>
	
	<title>RSS 2.0 Článků kategorie "PHP pro pokročilé"</title>
	<atom:link href="https://mike.treba.cz/rss/kategorie/php-pro-pokrocile/" rel="self" type="application/rss+xml" />
	<link>https://mike.treba.cz/</link>
	<description>RSS 2.0 Článků kategorie "PHP pro pokročilé"</description>
	<lastBuildDate>Sun, 21 Jun 2020 14:41:05 +1100</lastBuildDate>
	<language>cs</language>
	<generator>Abstract CMS</generator>
	<sy:updatePeriod>daily</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>

		
		<item>
			<title>Pokročilé stránkování v PHP</title>
			<link>https://mike.treba.cz/pokrocile-strankovani-php/</link>
			<pubDate>Sun, 21 Jun 2020 14:41:05 +1100</pubDate> 
			<comments>https://mike.treba.cz/pokrocile-strankovani-php/#comments</comments>
			<dc:creator></dc:creator>
			<description><![CDATA[<p>Za víc než 10 let, co svou stránkovací knihovnu používám, už prošla řadami změn. Od nepatrných, jako jsou bug fixy až po zásadní, jako například změna parametrů v konstruktoru. Některé nové funkce byly přidány, ale základní kostra je víceméně stejná. </p>]]></description>   
			<guid isPermaLink="false">https://mike.treba.cz/pokrocile-strankovani-php/</guid>
			<content:encoded><![CDATA[<p>Za víc než 10 let, co svou stránkovací knihovnu používám, už prošla řadami změn. Od nepatrných, jako jsou bug fixy až po zásadní, jako například změna parametrů v konstruktoru. Některé nové funkce byly přidány, ale základní kostra je víceméně stejná. </p> <p>Ano, už jste asi pochopili, že v následujícím článku vás nebudu učit, jak vypsat cyklem stránkování. Raději vám představím hotové řešení: takové, které na pár řádcích zařídí vše potřebné. Vy pak nemusíte řešit malichernosti a soustředit se na důležitější věci. Například optimalizace SQL dotazů. </p>
<p>Tak se jdeme podívat, jak na to. Článek byl poprvé publikován v roce 2010 a nyní, po rozsáhlé revizi stránkovací knihovny, ho znovu posouvám výš. Na PHP7 už to samozřejmě funguje. </p>
<h2>Základní nasazení</h2>
<p>Samotné nasazení jsem se snažil zredukovat na nutné minimum tak, abychom nemuseli volat hromady _set funkcí, a zároveň, aby šlo stále nastavit vše potřebné. </p>
<pre><code class="php">// SQL dotaz samozrejme upravim v souladu se svym frameworkem
$count = $mysqli-&gt;query("SELECT COUNT(*) FROM `tabulka`")-&gt;fetch_row();
$total = $count[0];

$paging_limit = 20;

// celkem | po kolika
$paging = new Paging($total, $paging_limit);
$paging-&gt;set_paging();

$paging_start = $paging-&gt;get_start();
$query = "SELECT * FROM `tabulka` LIMIT {$paging_start}, {$paging_limit}"; // -&gt; assoc_list

// simple vypis
echo $paging-&gt;export_paging();</code></pre>
<p>A to je vše. Jedním SQL dotazem spočítám záznamy, někde z nastavení vytáhnu stránkovací limit. Zbytek si knihovna zjistí sama. Výchozí proměnná pro stránkování je <strong>$_GET['page']</strong>, v případě potřeby jiné proměnné ji lze změnit v konstruktoru. Ukážu na dalším příkladu. Co se odkazů týče, vše je automatické. Ať už oddělovač ? nebo &amp;, tak doplnění celého query stringu, pokud ho chceme předávat dál. Vstup je automaticky ošetřen: _GET parametr je přetypován na int, takže ani na tohle už myslet nemusíme. </p>
<p>Na výše uvedeném příkladu dostaneme defaultní výstup. Jak ho změnit si ukážeme v rozšířeném nastavení.</p>
<h2>Rozšířené nastavení</h2>
<p>Kromě základních parametrů můžeme knihovně předat i spoustu dalších informací. Nejčastěji využijeme přejmenovaní GET proměnné. Nechceme-l GET['page'], stačí nastavit třetí argument. Podporu má i jednoduché pole. Umíme i vícerozměrné, nicméně takové věci už podle mého názoru nemají v URL co dělat. Ale pro jistotu. </p>
<p>Další důležité metody jsou <strong>set_paging_mode()</strong>, <strong>set_output_mode<strong>()</strong></strong> a <strong>set_around<strong>()</strong></strong>. Ty definují, jaký typ stránkování dostaneme na výstupu. Jednotlivé příklady najdete na konci článku. </p>
<p>Generování odkazů je automatické, vezme se REQUEST_URI a zkopíruje se celý GET vyjma definované proměnné. Pokud nastavím <strong>set_base_path()</strong>, automatické generování přestane. Dostanu pouze to, co jsem třídě předal, tedy například www.treba.cz/?page=2. V takovém případě pak další z metod, <strong>set_drop_vars<strong>()</strong></strong>, nemá význam. </p>
<p>Změnu generovaného HTML pomocí metody set_html_pattern() tu více rozvádět nebudu, ať mi jednoduchý návod nenakyne do obřích rozměrů. Na to se můžete podívat ve zdrojáku souboru.</p>
<p>A na závěr opět metoda set_paging, která si navíc umí pohlídat, jestli je volaná, kdy má. </p>
<pre><code class="php">$paging = new Paging($total, $paging_limit, 'filter[page]');
$paging-&gt;set_paging_mode(1)               // 1|0
       -&gt;set_output_mode(1)               // 1|0
       -&gt;set_around(2)                    // pri paging_mode=1
       -&gt;set_valid_links(true)            // replace [] pri slozenych odkazech

       -&gt;set_base_path($category_url)     // nechci generovat nic, predam zaklad odkazu
       -&gt;set_default_page(2)              // kdyz neni GET[page] zacinam na strane 2
       -&gt;set_drop_vars(array('ajax'))     // chci zkopirovat cely get krome ?ajax=
       -&gt;disable_translate_function(true) // mam funkci __() a dela neco jineho, nez ze akceptuje 1 string a vraci 1 string =&gt; nebudu prekladat

       -&gt;set_title_format($dt, $mb, $mn)  // desktop title, mobile back, mobile next
       -&gt;set_html_patterns($pattern)      // @see protected $pattern
       -&gt;set_paging();</code></pre>
<h3>Funkce __ <span class="small-text">(dvě podtržítka)</span></h3>
<p>Jednou z metod, které můžete či nemusíte zavolat, je také "<strong>disable_translate_function()</strong>". Protože počítám i s lokalizací titulků, testuji existenci funkce dvě podtržítka. Můžete ji najít například ve Wordpressu, používá ji i Kohana framework. Pokud ovšem takovou funkci máte k něčemu jinému, přidal jsem i možnost její volání vypnout. Stejně tak pro případ, že si chcete přeložit texty ještě před zavoláním stránkovací knihovny. Proto ani neřeším žádné callbacky. Pokud tuto funkci nemáte vůbec, neřešíte, protože volání je vždy podmíněno function_existem. </p>
<h3>Filosofická vsuvka</h3>
<p>Na chvíli se ještě vrátím zpět k parsování QUERY STRINGu a celkové logice předávání parametrů. Jako správně by to knihovna vůbec neměla dělat. Měla by dostat URL, referenci na jeden klíč a podle toho se zařídit. Jenže... To bychom pak v systému neřešili nic jiného, než které proměnné předat a které ne. Takhle máme možnost buď vše anebo nic. Funkce <em>set_drop_vars</em> je výjimka z tohoto pravidla, ale tady jsem si prostě chtěl ušetřit práci. Raději zvolím řešení, díky kterému nemusím na nic myslet a vím, že to skript vyřeší za mě. </p>
<h2>Výstup do šablony</h2>
<p>Jak je zmíněno výše, máme celkem 4 způsoby, jak stránkování vypsat. A k tomu navíc pátý pro telefon. Pokud se tedy nespokojíme se základním echo $paging; můžeme si na výstupu podmínit i alternativní výpis právě pro mobilní zařízení: namísto hromady odkazů jenom tlačítko "Následující" respektive "Předchozí". Jakoukoli další modifikaci zařídí styly, protože každý z elementů lze snadno a unikátně zacílit. </p>
<pre><code class="php">&lt;div class="paging"&gt;
	&lt;div class="hide-this-on-phone"&gt;
		&lt;?=$paging-&gt;export_paging();?&gt;
	&lt;/div&gt;
	&lt;div class="hide-this-on-desktop"&gt;
		&lt;?=$paging-&gt;export_mobile();?&gt;
	&lt;/div&gt;
&lt;/div&gt;</code></pre>
<p>A pokud jsme ještě náročnější a ani tohle nám nestačí, pak nám pomůže poslední funkce a my si můžeme s výstupem dělat cokoli, co se nám zlíbí. Ale jak říkám, tohle je jedna z věcí, kterou člověk opravdu nechce zaplácat jednoduchou šablonu. Když se sami podíváte na funkci <strong>export_paging()</strong>, fakt není úplně jednoduchá. Ale proti gustu... :-) </p>
<pre><code class="php">&lt;xmp&gt;
&lt;?php print_r($paging-&gt;export_data()); ?&gt;
&lt;/xmp&gt;</code></pre>
<h2>Varianty výstupu</h2>
<pre><code>export_paging();
1 ... 6 | 7 | 8  ... 50       (paging_mode = 1 | output_mode = 1) // vychozi
 &lt;&lt;  &lt;  5  &gt;  &gt;&gt;              (paging_mode = 1 | output_mode = 0 | vhodne dat set_around=1)
1 | 2 | 3 | 4 | 5             (paging_mode = 0 | output_mode = 0)
1-10 | 11-20 | 21-30 | 31-32  (paging_mode = 0 | output_mode = 1)

export_mobile();
&lt; Predchazejici  Nasledujici &gt;</code></pre>
<p>Na zdroják se můžete podívat nebo si ho stáhnout. Člověk by si řekl - že maličkost - ale je to pěkných 600 řádků kódu. </p>
<p class="download-links"><a href="https://mike.treba.cz/doc/2020/paging_lib/" target="_blank">class.Paging.php</a><br /><a href="https://mike.treba.cz/doc/2020/paging_lib/class.Paging2.zip">class.Paging.zip</a></p>]]></content:encoded>
			<image>
    				<url>https://mike.treba.cz/img/2020/web/php.jpeg</url>
			</image>
		</item>
		
		
		<item>
			<title>Správa redirectů s využitím databáze</title>
			<link>https://mike.treba.cz/redirect-php-mysql/</link>
			<pubDate>Sun, 8 Mar 2020 13:18:00 +1100</pubDate> 
			<comments>https://mike.treba.cz/redirect-php-mysql/#comments</comments>
			<dc:creator></dc:creator>
			<description><![CDATA[<p>Je tomu už příliš dlouho, co jsem naposledy psal nějaký článek z oboru, a tak je nejvyšší čas tento nedostatek napravit. Je tomu už podobně dlouho, co jsem naposledy něco skutečně programoval, a tak doufám, že můj kód nebude ostuda. Ale chtěl bych své články na téma zábava zase občas proložit něčím víc technickým. Většina mých článků je směrovaná na vývojáře začínající, dnes to bude ale spíš na ty středně pokročilé. Podíváme se na správu redirectů. </p>]]></description>   
			<guid isPermaLink="false">https://mike.treba.cz/redirect-php-mysql/</guid>
			<content:encoded><![CDATA[<p>Je tomu už příliš dlouho, co jsem naposledy psal nějaký článek z oboru, a tak je nejvyšší čas tento nedostatek napravit. Je tomu už podobně dlouho, co jsem naposledy něco skutečně programoval, a tak doufám, že můj kód nebude ostuda. Ale chtěl bych své články na téma zábava zase občas proložit něčím víc technickým. Většina mých článků je směrovaná na vývojáře začínající, dnes to bude ale spíš na ty středně pokročilé. Podíváme se na správu redirectů. </p> <p>Pokud jste úplný začátečník, těžko si nacpete následující kód do vlastního webu. (<em>V takové situaci doporučuji použít čistě <a href="mod_rewrite-a-hezke-url-dil-iii/">.htaccess.</a></em>) Používáte-li komplexní redakční systém, podobný plugin už nejspíš obsahuje. A pokud ne, můžete si vzít následující kód jako inspiraci, co změnit či upravit.</p>
<h2>Proč chceme přesměrovat</h2>
<p>Pokud jste přesměrování jedné adresy na druhou ještě nepotřebovali, pravděpodobně se svému webu málo věnujete :-) V opačném případě jistě víte, že změna URL adresy znamená ztrátu pozice v Googlu, kterou jste složitě budovali. A právě tohle vyřešíte přesměrováním s hlavičkou 301.</p>
<p>Možností jak danou funkcionalitu integrovat do systému je více a já vám dnes ukážu jednu z nich. Redirecty uložené v samotné tabulce mají jednu velkou výhodu, kterou je jejich přehlednost. Pokud bychom tyto informace ukládali automaticky a řešili je například na úrovní ukládání článku (<em>změní se url článku, uložím si automaticky redirect</em>), museli bychom provádět obrovské množství různých kontrol napříč mnoha tabulkami. A co víc, snadno ztratíme přehled, co směruje kam. Nemluvě o automatickém čištění všech předchozích redirectů s každým uložením.</p>
<p>Nevýhoda odděleného řešení je pak samozřejmě ta, že na to člověk musí myslet. Ale pokud se o svůj web staráte, tak URL, která skáče na první stránce v Googlu asi neměníte každý den. A nastavit případný redirect člověk jistě nezapomene.</p>
<h3>Kód #1: SQL</h3>
<p>Začneme tabulkou v databázi. Ukládat chceme výchozí url, cílovou url, checkbox pro přesnou shodu, checkbox pro přesnou náhradu (<em>vysvětlím níže</em>) a poznámku. Ta bude sloužit ke krátkému popisu, proč redirect vlastně zakládám. Na závěr samozřejmě možnost deaktivace a čas vytvoření záznamu. <em>original_url</em> chceme mít jako unikátní klíč, protože více redirectů ze stejné adresy je nesmysl. </p>
<pre><code class="sql">CREATE TABLE IF NOT EXISTS `project_redirects` (
	`id` int(10) unsigned NOT NULL   AUTO_INCREMENT,
	`original_url` varchar(255) NOT NULL,
	`redirect_url` varchar(255) NOT NULL,
	`exact_match` tinyint(1) DEFAULT '1',
	`do_replace` tinyint(1)  DEFAULT '0',
	`note` varchar(255) DEFAULT NULL,
	`active` tinyint(1) DEFAULT '1',
	`timestamp` timestamp  DEFAULT CURRENT_TIMESTAMP,
	PRIMARY KEY (`id`),
	UNIQUE KEY `original_url` (`original_url`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 ;</code></pre>
<h2>Jak to bude fungovat </h2>
<p>Ze struktury tabulky už nejspíš pochopíte i chování. Uložíme původní a cílovou url a při každém načtení stránky zjistíme, jestli má aktuální URL kam přesměrovat. Porovnáme vždy relativní i absolutní variantu cesty.</p>
<p>Pole <em>exact_match</em> nám řekne, jestli budeme testovat přesnou shodu nebo nasadíme funkci <em>strpos</em>. Pole <em>do_replace</em> následně definuje, jestli provádíme klasický <em>str_replace</em> nebo chceme směrovat vše na novou url. </p>
<p><strong>Jaké případy tedy umíme podchytit?</strong></p>
<ul>
<li>Jedna konkrétní adresa na novou adresu: <em>/kategorie/ =&gt; /nova-kategorie/</em></li>
<li>Zahodíme celou větev a přesměrujeme na novou adresu: <em>/kategorie/vše/</em> =&gt; <em>/nova-kategorie/</em></li>
<li>Celá větev změní jen část své adresy: <em>/kategorie/cokoli/</em> =&gt; <em>/nova-kategorie/cokoli/</em></li>
</ul>
<p>Porovnání přes strpos === 0 zaručí, že se podmínka chytí jenom na adresy, které daným stringem začínají. Takže když bude stejný string zanořený někde hlouběji, nic se nestane. Vše je navíc navrženo tak, abych tu <em>root_url</em> nemusel nikam ukládat, ať u případné změny domény neřeším absolutní cesty bůhví kde v databázi. </p>
<h3>Kód #2: .htaccess</h3>
<p>Abychom mohli provádět efektivnější redirecty, jsou potřeba i nějaké ty "<a href="mod_rewrite-a-hezke-url/">hezké url</a>". Následující zápis nasměruje vše, co není existující soubor nebo složka na index. Pro spuštění v podadresáři je pak potřeba přidat i <strong>RewriteBase</strong>. </p>
<pre>RewriteEngineOn<br />#RewriteBase /redir/
RewriteCond %{REQUEST_FILENAME} !-d 
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?path=$1 [L,QSA]</pre>
<h3>Kód #3: základní proměnné, funkce a připojení k databázi</h3>
<p>Zpracujeme primární GET proměnnou, nastavíme ROOT a připojíme se k databázi. Databázi si jistě vyřeší každý po svém, tady jsem uvedl jen minimum kódu tak, aby ukázka fungovala. <em>(Pro sešny platí to samé.)</em> Funkce <strong>get_url</strong> nám pak vrátí celou absolutní URL, na které se zrovna nacházíme. </p>
<pre><code class="php">$_root_url = 'http://localhost/redir/';
$_path = (!empty($_GET['path']) ? (string)$_GET['path'] : '');

$config['db_host'] = 'localhost'; 
$config['db_user'] = 'root';
$config['db_pass'] = ''; 
$config['db_name'] = 'evil';

$sqli = new mysqli($config['db_host'], $config['db_user'], $config['db_pass'], $config['db_name']);
$sqli-&gt;set_charset('utf8');
if(!empty($sqli-&gt;connect_error)){
 	exit($sqli-&gt;connect_error);
}

session_start();

function get_url($header = false){
	$pure_url = null;
	$html_url = null;
	
	if(!$pure_url){
		$url = (isset($_SERVER['HTTPS']) ? 'https://' : 'http://');
		$url .= $_SERVER['SERVER_NAME'];
		$port = explode(':', $_SERVER['HTTP_HOST']);
		if(!empty($port[1])){
			$url .= ':'.$port[1];
		}
		$url .= $_SERVER['REQUEST_URI'];
		$pure_url = $url;
		$html_url = str_replace('&amp;', '&amp;', $pure_url);
	}
	
	return $header ? $pure_url : $html_url;
}</code></pre>
<h3>Kód #4: Samotná funkce</h3>
<p>Hned první blok kódu vznikl po analýze adres, kterou často navštěvují spamboti. Jakože fakt často. Takže mezera (<em>respektive plus</em>) hranatá závorka se automaticky přesměruje na <strong>root</strong>. </p>
<p>Pak už jen vytáhneme všechny existující redirecty z databáze a pustíme se do kontroly. SQL dotaz by šel určitě lépe vydefinovat tak, aby se netahaly úplně všechny řádky, ale na druhou stranu počítám s nějakou lidsky rozumnou správou. Fakt tam nechceme mít stovky řádků. Což bychom ani mít neměli, když je možnost vypnout <em>exact_match</em> a obsáhnout i větší množství záznamů jedním řádkem. </p>
<p>V cyklu podle <em>exact_match</em> zvolíme způsob porovnání. No a následuje samotné ošetření smyčky. <strong>Redirect loop</strong> totiž není příliš fajn věc, protože vám to zpravidla zahlásí až prohlížeč. Žádnou chybovku od serveru nedostaneme, což se opravdu špatně ladí. Třeba Chrome už se v poslední době umoudřil a stránku zabije poměrně rychle, ovšem na to se nedá vždy spolehnout. Raději si tam podstrčím hlášku vlastní, abych hned viděl, kde je problém. Stejné ošetření pak můžeme provádět během jakéhokoli systémového redirectu. </p>
<pre><code class="php">// redirekty z tabulky
// plus nejake natvrdo
function redirect_check($_path, $_root_url, $sqli){
	
	// tohle delaji spamboti
	if(strpos($_path,' [') !== false){
		header('Location: ' . $_root_url);
		return false;
	}
	
	$redir_query = $sqli-&gt;query("SELECT original_url, redirect_url, exact_match, do_replace FROM project_redirects WHERE `active` = 1 ORDER BY id DESC");
	$redirects = array();
	while($row = $redir_query-&gt;fetch_assoc()){
		$redirects[] = $row;
	}
	
	// podminka vzdy pro relativni i absolutni URL
	// vse bud relativni nebo absolutni
	// neni-li exact_match, testujeme jestli url danym retezcem zacina, at to nereplacuje kdekoli
	// neni-li replace (default 0), pak to redirektuje vse co danym stringem zacina na cilovou url
	foreach($redirects as $item){
		if(!empty($item['exact_match'])){
			
			if(($_path == $item['original_url']) || (get_url(true) == $item['original_url'])){
				$redirect_target = $item['redirect_url'];
			}
			
		}else{
			
			if((strpos(get_url(true), $item['original_url']) === 0) || (strpos($_path, $item['original_url']) === 0)){
				if(!empty($item['do_replace'])){
					$redirect_target = str_replace($item['original_url'], $item['redirect_url'], $_path);
				}else{
					$redirect_target = $item['redirect_url'];
				}
			}	
			
		}
	}
</code></pre>
<pre><code class="php">	
	if(!empty($redirect_target)){
	
		// zastupny znak pro redirekt na homepage
		if($redirect_target == '/'){ 
			$redirect_target = $_root_url;
		}
		
		if((strpos($redirect_target, 'http://') === false) &amp;&amp; (strpos($redirect_target, 'https://') === false)){
			$redirect_target = $_root_url . $redirect_target;
		}
		
		// redirect_happened bude vzdy prazdny, pokud nedojde k zacykleni
		if(!empty($_SESSION['redirect_happened']) &amp;&amp; $_SESSION['redirect_target_url'] == get_url(true)){		
			
			// potreba vynulovat
			// aby to zase nechciplo, kdyz to v administraci opravim
			// cela konstrukce musi byt obalena v try catch
			// musi se porovnavat URL, nestaci 1/0, kvuli requestovym spam botum
			$exception_message = 'Redirect linking to another.'; 
			$exception_message.= '&lt;br&gt;Previous URL: ' . $_SESSION['redirect_prev_url'];
			$exception_message.= '&lt;br&gt;Next URL: ' . $_SESSION['redirect_target_url'];

			$_SESSION['redirect_happened']   = false;
			$_SESSION['redirect_target_url'] = null;
			$_SESSION['redirect_prev_url']   = null;
			
			exit($exception_message);
		}
		
		$_SESSION['redirect_happened']   = true;
		$_SESSION['redirect_target_url'] = $redirect_target;
		$_SESSION['redirect_prev_url']   = get_url(true);
		
		header('Location: ' . $redirect_target, true, 301);
		return;
	}
	
	$_SESSION['redirect_happened']   = false;
	$_SESSION['redirect_target_url'] = null;
	$_SESSION['redirect_prev_url']   = null;
</code></pre>
<pre><code class="php">		
	// doplneni lomitka
	// lze vyresit komplexeni : ale pro ukazku staci
	if(!empty($_path) &amp;&amp; substr($_path, -1) != '/'){
		if((strpos($_SERVER['REQUEST_URI'], '?') === false) &amp;&amp; (strpos($_SERVER['REQUEST_URI'], '&amp;') === false)){
			
			// u nekterych url treba lomitko nechceme
			if($_path != 'sitemap.xml'){
				header('Location: ' . get_url() . '/', false);
				return;
			}
		}
	}
	
	return $redirects;
}

$_redirects = redirect_check($_path, $_root_url, $sqli);</code></pre>
<h2>Ošetříme zacyklení</h2>
<blockquote>
<p>Funkce běží a zjistí, jestli má proběhnout redirect. V tu chvíli se zeptá: neproběhl už naposled nějaký?</p>
</blockquote>
<p>Nastavíme do _SESSION potřebné hodnoty a zabijeme skript. </p>
<p>Pokud podmínka splněna není, tedy je vše v pořádku, proměnné v _SESSION zase vynulujeme. Protože kdybychom to neudělali, hodnoty zůstanou uložené a bude se to cyklit i v případě, že chybné přesměrování v administraci opravíme.</p>
<p>A protože vynulování proměnných nastane jen tehdy, je-li vše v naprostém pořádku, máme automaticky ošetřené i víceúrovňové zacyklení: tedy když odkaz 1 směruje na odkaz 2, ten na odkaz 3 a ten zase zpátky na 1. Jednoduše se testuje, jestli jeden redirect nesměřuje na jiný. Je jedno, jestli to po dvou / třech přesměrováních skončí na normální adrese nebo ve smyčce. Nechceme ani jedno. </p>
<p>Na řádek s exitem samozřejmě doporučuji nějaké pokročilé odchytávání výjimek: např. logovací soubor nebo odeslání mailu.</p>
<p>Závěrem ještě doplníme lomítko, protože je s tím méně ladění, než to řešit v .htaccessu. </p>
<h3>Kód #5: Testovací data</h3>
<pre><code class="sql">INSERT INTO `project_redirects` (`id`, `original_url`, `redirect_url`, `exact_match`, `do_replace`, `note`, `active`, `timestamp`) VALUES
(1, 'test-url-1/', 'test-url-2/', 1, 0, 'test', 1, '2020-02-02 13:37:33'),
(2, 'test-url-2/', 'test-url-1/', 1, 0, 'test zacykleni', 1, '2020-02-02 13:37:33'),
(3, 'kategorie/', 'nova-kategorie/', 0, 0, 'vse na jednu jedinou url', 1, '2020-02-02 13:37:33'),
(4, 'kategorie-x/', 'nova-kategorie-x/', 0, 1, 'vcetne deti', 1, '2020-02-02 13:37:33'),
(5, 'nesmysl/', '/', 1, 0, 'test redirectu na root', 1, '2020-02-02 13:37:33');</code></pre>
<p>A na úplný závěr ještě jeden tip: při testování redirectů doporučuji vždy smazat historii prohlížeče, protože si ty redirecty cachují. Což má jít vyřešit zasláním no-cache v headeru a exitem, nicméně to není bohužel stoprocentní. </p>
<p class="download-links"><a href="https://mike.treba.cz/doc/2020/redirects/index.php" target="_blank">index.php</a><br /><a href="https://mike.treba.cz/doc/2020/redirects/htaccess.txt" target="_blank">.htaccess</a><br /><a href="https://mike.treba.cz/doc/2020/redirects/redirects.sql" target="_blank">redirect.sql</a></p>
<p class="download-links"><a href="https://mike.treba.cz/doc/2020/redirects/redirects.zip" target="_blank">ZIP archív ke stažení</a></p>]]></content:encoded>
			<image>
    				<url>https://mike.treba.cz/img/2020/web/abstract-business-code-coder-270348.jpg</url>
			</image>
		</item>
		
		
		<item>
			<title>Objekty v PHP5 - Skládání objektů, abstrakce a dědičnost</title>
			<link>https://mike.treba.cz/objekty-v-php5-skladani-objektu-abstrakce-dedicnost/</link>
			<pubDate>Wed, 16 Jul 2014 22:11:16 +1100</pubDate> 
			<comments>https://mike.treba.cz/objekty-v-php5-skladani-objektu-abstrakce-dedicnost/#comments</comments>
			<dc:creator></dc:creator>
			<description><![CDATA[<p>V dnešním článku si povíme něco víc o objektech, a to konkrétně o<strong> skládání objektů</strong>, <strong>dědičnosti</strong>, <strong>abstrakci </strong>a samozřejmě i <strong>viditelnosti</strong>, jejíž znalost je k výše uvedeným technikám nezbytná. </p>]]></description>   
			<guid isPermaLink="false">https://mike.treba.cz/objekty-v-php5-skladani-objektu-abstrakce-dedicnost/</guid>
			<content:encoded><![CDATA[<p>V dnešním článku si povíme něco víc o objektech, a to konkrétně o<strong> skládání objektů</strong>, <strong>dědičnosti</strong>, <strong>abstrakci </strong>a samozřejmě i <strong>viditelnosti</strong>, jejíž znalost je k výše uvedeným technikám nezbytná. </p> <p>Na první pohled se může zdát o složité termíny, ale jejich samotné použití nijak náročné není. Mnohem složitější je <em>správné použití</em>. Nepřidávat dědičnost jen proto, abychom ukázali, že ji umíme, nenaplácat všechna klíčová slova do jedné třídy jenom aby tam byly. To je také hlavní důvod, proč píšu samostatný text, důvod, proč nemohu vše demonstrovat na své databázové třídě.</p>
<h2>Skládání objektů</h2>
<p>Objekty skládáme ve chvíli, kdy třída A uloží do členské proměnné instanci třídy B a do jejího konstruktoru<strong> předá sama sebe</strong>. Tímto způsobem to může jít pořád dál, takže v každé třídě aplikace budeme mít přístup k veřejným metodám všech ostatních tříd. Předání objektu neprobíhá přes kopii jako u proměnných, nýbrž přes <strong>referenci</strong>. Nevzniká X kopií databázové třídy, ale pouze jedna, a jakákoli změna nastavení se projeví ve všech referencích na daný objekt.</p>
<p>Tato definice ale zní strašně krkolomně, takže si ukažme příklad ze života.</p>
<p>Mějme třídu <strong>Environment</strong>, která vytváří základní prostředí aplikace. V členských proměnných má uložené instance některých tříd, které budou volány napříč celou aplikací. V našem příkladu to bude třída Db (<em>databáze</em>) a třída Debug (<em>s nějakým uživatelským výstupem pro ladění</em>). Třída Env pak vytvoří instanci samotného modulu, který už se bude starat o obsluhu konkrétní části webu. V ukázce je kontroler pro administraci. Ten vytvoří instanci další třídy, která už bude zpracovávat jednotlivé části administračního prostředí: kategorie, články, produkty, formuláře... Třída Db je prázdná, aby nám ukázka neházela fatal error, <strong>Debug </strong>obsahuje akorát metodu s funkcí <em>print_r()</em> pro ukázku rekurzivního předávání objektů.</p>
<blockquote>
<p>Takže pokaždé, kdy postoupíme hlouběji do aplikace, daná třída předá sama sebe dál.</p>
</blockquote>
<p>Je samozřejmě důležité, aby všechny parametry obsahující referenci na jiný objekt měly viditelnost <strong>public </strong>- pokud tedy nechceme přístup explicitně zakázat. Kostra by mohla vypadat například takto:</p>
<pre><code class="php">class Db { }

class Debug {

	public $env;
	
	public function __construct(Environment $env){
		$this-&gt;env = $env;
	}
	
	public function printDebug(){
		print_r($this);
	}
}

class Environment {

	public $db;
	public $debug;
	public $module;
	
	public function __construct(){
		$this-&gt;db = new Db();
		$this-&gt;debug = new Debug($this);
		$this-&gt;module = new MasterAdminController($this);
	}
}

class MasterAdminController {

	public $env;
	public $localModule;
	
	public function __construct(Environment $env){
		$this-&gt;env = $env;
		
		$this-&gt;localModule = new ArticleAdminController($this);
	}
}

class ArticleAdminController {

	public $parentModule;
	
	public function __construct($parent_controller){
		$this-&gt;parentModule = $parent_controller;
		
		// pristup k tride DB
		$db = $this-&gt;parentModule-&gt;env-&gt;db; // -&gt;metoda();
	}
}</code></pre>
<p>Nyní si ukážeme, co se stane, když zavoláme následující kód:</p>
<pre><code class="php">$env = new Environment;

echo "&lt;pre&gt;";
print_r($env);
echo "&lt;/pre&gt;";

echo "&lt;pre&gt;";
print_r($env-&gt;debug-&gt;printDebug());
echo "&lt;/pre&gt;";</code></pre>
<p>Oba bloky vypíšou skoro to stejné. Klíčové slovíčko *RECURSION* nám potvrdí, že objekty jsou vázány opravdu přes referenci.</p>
<pre><code>Environment Object
(
    [db] =&gt; Db Object
        (
        )

    [debug] =&gt; Debug Object
        (
            [env] =&gt; Environment Object
 *RECURSION*
        )

    [module] =&gt; MasterAdminController Object
        (
            [env] =&gt; Environment Object
 *RECURSION*
            [localModule] =&gt; ArticleAdminController Object
                (
                    [parentModule] =&gt; MasterAdminController Object
 *RECURSION*
                )
        )
)

Debug Object
(
    [env] =&gt; Environment Object
        (
            [db] =&gt; Db Object
                (
                )

            [debug] =&gt; Debug Object
 *RECURSION*
            [module] =&gt; MasterAdminController Object
                (
                    [env] =&gt; Environment Object
 *RECURSION*
                    [localModule] =&gt; ArticleAdminController Object
                        (
                            [parentModule] =&gt; MasterAdminController Object
 *RECURSION*
                        )
                )
        )
)</code></pre>
<h2>Dědičnost</h2>
<p>Abychom pochopili, jak správně využít dědičnost, budeme si muset vysvětlit některé pojmy a stejně tak si povědět o základním smyslu dědění tříd. Příklad bych opět demonstroval na kostře projektu, na které jsme si popsali skládání. Přeskočíme třídu prostředí a začneme rovnou moduly. Mějme tedy instanci modulu pro administraci, která pak načítá už konkrétní obsluhy jednotlivých funkčních celků.</p>
<p>Chceme-li spravovat například již zmíněné <strong>články</strong>, <strong>kategorie </strong>a <strong>produkty</strong>, jistá část skriptů bude vždy podobná až stejná. Jiné části se budou lišit, a to konkrétními bloky kódu, které v jiných modulech obsaženy nebudou. A tak si navrhneme jakousi centrální třídu, kterou dané moduly zdědí.</p>
<p>Tím vlastně docílíme stejné logiky každého skriptu. Rodičovská třída bude obsahovat metody a parametry společné pro každý modul, její potomci ji pak vždy rozšíří o metody sobě specifické. Tím nejen ušetříme spoustu řádků kódu, ale také donutíme sami sebe psát ten kód tak říkajíc "slušně". Budeme muset dodržovat předem stanovenou logiku.</p>
<p><strong>Druhý případ</strong> využití dědičnosti je už mnohem jednodušší. Máme knihovnu, do které potřebujeme přidat funkčnost. A jelikož knihovny nejsou určeny k tomu, aby se do nich hrabalo, založíme třídu novou, která knihovnu rozšíří. Můžeme vzít <a href="objekty-v-php5-navrh-databazove-tridy-dil-i-uvod-cile/">databázovou třídu</a> a přidáme funkce pro zasílání chyb na e-mail. Tento způsob se často využívá v situacích, kdy jsou knihovny umístěné v nějakém centrálním úložišti, my je známe, můžeme je <strong>includovat </strong>ale nemůžeme tyto soubory měnit.</p>
<p>Na následujícím příkladě si ukážeme stav první. Ale zpět k důležitým pojmům. Než se dostanu ke kódu, je potřeba si tyto pojmy vysvětlit.</p>
<h2>Viditelnost</h2>
<p>PHP nabízí tři úrovně viditelnosti: <strong>public</strong>, <strong>protected </strong>a <strong>private</strong>. Public (<em>veřejný</em>) znamená, že k metodám či atributům třídy můžeme přistupovat z venčí. Protected (<em>chráněný</em>) toto omezí pouze na potomky: k funkci nepůjde přistupovat z venčí, ale pokud třídu zdědíme, ke všemu se dostaneme. Často se říká, že pokud chceme metody respektive atributy využít jen interně, dáme všechno protected. Nejvyšší stupeň utajení, private (<em>soukromý</em>), pak omezí přístup pouze na danou třídu. I když ji zdědíme, k metodám se nedostaneme.</p>
<h2>Abstrakce</h2>
<p>Klíčové slovo <strong>abstract </strong>definuje, že třída musí být zděděná. Nelze vytvořit její instance, jakýkoli pokus selže chybou. Pakliže je třída definovaná jako abstraktní, může obsahovat i abstrakní metody. Abstraktní metoda má pouze definici, nemá vlastní tělo funkce. Tím řekneme, že potomek musí danou funkci obsahovat. Jaké bude její tělo už ale neurčujeme. Asi by bylo dobré v daném kontextu i vysvětlit rozdíl mezi <strong>rozhraním </strong>(interface) a <strong>abstraktní třídou.</strong></p>
<blockquote>
<p><strong>Rozhraní </strong>je šablona vzoru, osnova. <strong>Abstraktní třída</strong> už může být knihovna nebo modul, musí se ale "dokončit" jejím zděděním a dopsáním prázdných metod.</p>
</blockquote>
<h2>Klíčové slovo "final"</h2>
<p>Naopak klíčové slovíčko <strong>final </strong>jakoukoli dědičnost zakazuje. Třída už je kompletní a nechce být žádným způsobem rozšiřována.</p>
<pre><code class="php">abstract class AdminController {

	public $parentModule;
	
	protected $path = array();
	
	private $someParam;
	
	final public function __construct($parent_module = null){
		$this-&gt;parentModule = $parent_module;
	}
	
	public function loadData($param){
		echo 'load some data';
	}
		
	abstract public function __init();
}

class CategoryAdminController extends AdminController {
	public function __init(){
		// samotne spusteni
	}
	
	public function loadData($param = null){
		parent::loadData($param);
		echo ' and do something';
	}
	
	public function loadAnotherData($param){
		echo 'load another data';
	}
}

$cont = new CategoryAdminController();
$cont-&gt;loadData();</code></pre>
<p>Na výše uvedeném příkladu je ukázaná možnost redefinovat metody rodiče v potomkovi, což je přesně ten typ rozšíření, kvůli kterému vůbec chceme něco dědit. Ukázka vypíše přesně toto:</p>
<pre><code>load some data and do something</code></pre>
<p>Na závěr přikládám použitý kód ke stažení, i když si myslím, že zde to ani tolik nutné není.</p>
<p><strong>Odkaz</strong><span>:</span></p>
<p class="download-links"><a href="doc/2014/dedeni_skladani_sample.zip">dedeni_skladani_sample.zip</a></p>]]></content:encoded>
			<image>
    				<url>https://mike.treba.cz/img/2023/renewed/code-funny.jpg</url>
			</image>
		</item>
		
		
		<item>
			<title>Návrh databázové třídy - díl VI - Závěr</title>
			<link>https://mike.treba.cz/navrh-databazove-tridy-dil-vi-zaver/</link>
			<pubDate>Mon, 14 Jul 2014 20:31:52 +1100</pubDate> 
			<comments>https://mike.treba.cz/navrh-databazove-tridy-dil-vi-zaver/#comments</comments>
			<dc:creator></dc:creator>
			<description><![CDATA[<p>Šestým dílem bych rád uzavřel svůj seriál o databázové třídě a úvod do <strong>objektového programování v PHP5</strong>. Ukázali jsme si středně složitou třídu s obsluhou většiny typů SQL dotazů, které využijeme pro projekt průměrné náročnosti. </p>]]></description>   
			<guid isPermaLink="false">https://mike.treba.cz/navrh-databazove-tridy-dil-vi-zaver/</guid>
			<content:encoded><![CDATA[<p>Šestým dílem bych rád uzavřel svůj seriál o databázové třídě a úvod do <strong>objektového programování v PHP5</strong>. Ukázali jsme si středně složitou třídu s obsluhou většiny typů SQL dotazů, které využijeme pro projekt průměrné náročnosti. </p> <p>Kromě naprosto základních funkcí jako ošetření SQL dotazů proti <strong>SQL injection</strong> umí i jednoduché logování, export databáze buď do souboru nebo přímo do force downloadu i vlastní obsluhu chyb. Posílání chyb na e-mail zde ale obsažené není: na notifikační e-maily mám třídu další, o které si můžeme povědět v jiném článku. Nesmím také vynechat možnost <strong>připojení k více databázím</strong> v rámci jednoho projektu: i to <strong>class Db</strong> zvládne. Tak hurá na rekapitulaci.</p>
<h2>Vytvoření instance</h2>
<p>Přihlašovací údaje k databázi nastavíme v globálních proměnných. Klíč proměnné <strong>config</strong>, ve kterém bude třída hledat potřebná data můžeme změnit přímo v parametru konstruktoru; tímto pak můžeme vytvořit připojení k více databázím.</p>
<pre><code class="php">$config['db']['host'] // db server
$config['db']['user'] // uzivatel
$config['db']['pass'] // heslo
$config['db']['name'] // jmeno databaze

$db = new Db;</code></pre>
<p>Nastavení ještě umožňuje zadání tří dalších nepovinných hodnot, které ale najdete až v komentářích ve zdrojové kódu.</p>
<h2>Základní metody pro SQL dotazy</h2>
<pre><code class="php">// asociativni pole z tabulky, zakladni (pokrocile nastaveni @227)
$query = $db-&gt;query("SELECT * FROM ?_tabulka WHERE id = '?' AND category_id = '?'", array($id, $category_id))-&gt;assocList();

// insert, vraci mysql_insert_id()
$insert_id = $db-&gt;insert('?_categories', array( 
    'name' =&gt; $_POST['name'], 
    'text' =&gt; $_POST['text'], 
));

// update, zakladni (pokrocile nastaveni @499)
$affected_rows = $db-&gt;update('?_categories', $_GET['id'], array( 
    'name' =&gt; $_POST['name'], 
    'text' =&gt; $_POST['text'], 
)); 
 
// delete, vraci opet mysql_affected_rows();
$deleted_rows = $db-&gt;delete('?_articles', array( 
    'parent_id' =&gt; 1, 
));</code></pre>
<h2>Debug</h2>
<p>Jak jsem zmínil v úvodu, knihovna má obsažený i pár debugovacích funkcí pro případné <strong>ladění rychlosti</strong> SQL dotazů. Umí vypsat počet provedených dotazů &plus; pole s jejich kompletním zněním i časem, jak dlouho který dotaz trval.</p>
<pre><code class="php">// debug
echo "&lt;pre&gt;";
echo "pocet provedenych dotazu: ".$db-&gt;numQueries();
print_r($db-&gt;getQueries());
echo "&lt;/pre&gt;";</code></pre>
<h2>Export databáze</h2>
<p>V neposlední řadě můžeme provést i export databáze. Buď celé nebo jen konkrétních tabulek či konkrétních částí daných tabulek. Tuto funkci nedoporučuji přidávat dynamicky do aplikace: sám ji používám vždy v podobě samostatného souboru kam kód i podmínky napíšu ručně, takže i vám doporučuji stejnou logiku použití.</p>
<pre><code class="php">// export databaze
$db = new Db; 
//$db-&gt;addExportCondition('shop_categories', ''); 
//$db-&gt;addExportCondition('shop_products', 'WHERE category_id IN (1,2,3)'); 
$db-&gt;dumpAndSave();
//$db-&gt;dumpAndDownload();</code></pre>
<p><strong>Odkazy</strong>:</p>
<p class="download-links">Zdrojové kód: <a href="doc/2014/class.Db2.html" target="_blank">class.Db2.phps</a><br />ZIP ke stažení: <a href="doc/2014/class.Db2.zip">class.Db2.zip</a></p>
<blockquote>
<p><em><em>Edit 4. 5. 2020: </em><br />Text se týká PHP 5. Pod PHP 7 už třída fungovat nebude, protože všechny mysql_ funkce skončí chybou.</em></p>
</blockquote>]]></content:encoded>
			<image>
    				<url>https://mike.treba.cz/img/2023/renewed/code.jpg</url>
			</image>
		</item>
		
		
		<item>
			<title>Návrh databázové třídy - díl V - Logování změn</title>
			<link>https://mike.treba.cz/php-oop-databazova-trida-dil-5-logovani/</link>
			<pubDate>Wed, 9 Jul 2014 10:01:53 +1100</pubDate> 
			<comments>https://mike.treba.cz/php-oop-databazova-trida-dil-5-logovani/#comments</comments>
			<dc:creator></dc:creator>
			<description><![CDATA[<p>V dalším pokračování seriálu o objektovém programování v PHP si ukážeme, jak přidat do ukázkové třídy DB logování konkrétních SQL dotazů. Rozebereme si tedy znovu metody na <strong>insert</strong>, <strong>update </strong>a <strong>delete</strong>, které rozšíříme o další zápis do nové tabulky, kam budeme ukládat informace o provedených změnách. </p>]]></description>   
			<guid isPermaLink="false">https://mike.treba.cz/php-oop-databazova-trida-dil-5-logovani/</guid>
			<content:encoded><![CDATA[<p>V dalším pokračování seriálu o objektovém programování v PHP si ukážeme, jak přidat do ukázkové třídy DB logování konkrétních SQL dotazů. Rozebereme si tedy znovu metody na <strong>insert</strong>, <strong>update </strong>a <strong>delete</strong>, které rozšíříme o další zápis do nové tabulky, kam budeme ukládat informace o provedených změnách. </p> <p>Nejdřív ale krátká rekapitulace. Soubor článků o objektech v PHP jsem začal psát už poměrně dávno, ještě před pauzou v blogování. Jelikož jsem měl ale na články vcelku pozitivní ohlasy, byla by asi škoda seriál nedokončit, byť opožděně.</p>
<p><a href="objekty-v-php5-navrh-databazove-tridy-dil-i-uvod-cile/">V prvním dílu</a> jsme si stanovili cíle a řekli si něco o rozhraní nad třídou. <a href="navrh-databazove-tridy-dil-ii-zakladni-metody/">V dílu druhém</a> jsme si ukázali základní metody na dotazy typu <strong>select</strong>, <strong>insert</strong>, <strong>update </strong>a <strong>delete</strong>, které si dnes připomeneme a upravíme. <a href="navrh-databazove-tridy-dil-iii-class-query/">Třetí díl</a> byl vlastně o skládání objektů, a to ukázka třídy Query, jejíž instanci vracely metody mateřské třídy Db. <a href="navrh-databazove-tridy-dil-iv-export-databaze/">Poslední, čtvrtý díl</a> byl spíš takový návod jak jednoduše exportovat databázi bez přístupu do PHPMyAdmina; hodně kódu a méně povídání.</p>
<p>Dnešní článek bude podobně stavěný jako ten předchozí. Ukážeme si, jak logovat SQL dotazy, povíme si proč spolu s vysvětlením, u kterých metod má rozšíření o logování smysl a u kterých ne. V seriálu budou následovat <strong>ještě 2 další články</strong>: slovo závěrem s odkazem na celý <strong>zdrojový kód ke stažení</strong> a navíc ještě jeden krátký text o dědičnosti. Té jsem se zatím nevěnoval a jelikož se jedná o poměrně důležitou kapitolu v objektově orientovaném programování, zaslouží si článek navíc.</p>
<h2>Princip logování a kdy má smysl</h2>
<p>Základní princip je poměrně triviální: u každého provedeného dotazu zkopírujeme celé jeho znění do další tabulky. Metoda insert poskládá <strong>SQL dotaz</strong> dle zadaných parametrů a provede ho vlastně na 2 tabulkách. Metody update a delete pak upraví respektive smažou záznam z databáze spolu se zavoláním dalšího insertu na pozadí do logovací tabulky. U žádných dalších metod pak logování samozřejmě nemá význam.</p>
<p>Samotné logování má smysl v jednom jediném případě, a to v <strong>administraci </strong>či na jiných místech aplikace, kde mají <strong>uživatelé </strong>přístup k úpravě dat. Ve výsledku totiž dostaneme takovou jednoduchou historii provedených změn. Chtěl bych zdůraznit, že v žádném případě nepůjde o nějaké revize článků či kategorií, to je samo o sobě neskutečně komplexní funkcionalita, kterou rozhodně neobslouží pár metod v databázové třídě.</p>
<h2>Využití v praxi</h2>
<p>Pokud si spravujeme vlastní malé stránky jako například nyní já a stane se nám nehoda, kdy si například omylem umažeme kus napsaného textu, v databázi pak snadno dohledáme předchozí provedený <strong>update</strong> ainformace zachráníme. Pakliže má do administrace přístup více uživatelů, můžeme velice snadno zjistit, kdo kde co rozbil :-) Tento případ se může na první pohled zdát poněkud úsměvný, ale z praxe vím, že k takovýmto situacím dochází dnes a denně... <strong>Jako jeden z příkladů</strong> mohu uvést editaci uživatelského profilu na e-shopu. Může se stát, že zákazník odešle objednávku, další den si v profilu upraví adresu a pak se bude hádat, že mu objednávka byla odeslána na adresu špatnou. A člověk je pak zbytečně za blbce. Bohužel, i tak to chodí. Takže je fajn mít tyto stavy podchycené.</p>
<h2>Struktura tabulek</h2>
<p>Dost bylo teorie, přejdeme k samotnému kódu. V databázi bude potřeba založit 2 nové tabulky.</p>
<pre><code class="sql">CREATE TABLE `project_log` (
  `id` bigint(20) unsigned  auto_increment,
  `user_id` int(10) default '0',
  `table_name` varchar(64) NOT NULL,
  `method` varchar(16) NOT NULL,
  `insert_id` bigint(20) DEFAULT NULL,
  `whole_query` longtext,
  `timestamp` timestamp  default CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`),
  KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;

CREATE TABLE `project_log_items` (
  `id` bigint(20) unsigned  auto_increment,
  `parent_id` bigint(20) unsigned NOT NULL,
  `col_name` text,
  `col_value` text,
  PRIMARY KEY  (`id`),
  KEY (`parent_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ; </code></pre>
<p>První bude obsahovat základní informace o provedeném dotazu: ID uživatele referující na interní tabulku v daném systému, jméno tabulky, kde úprava proběhla, název fukce, která logování zavolala, ID záznamu pro dotazy typu insert, celé znění SQL dotazu a informaci o čase. Do druhé tabulky s referencí na první pak budeme ukládat veškeré údaje sloupec po sloupci. Díky tomu pak budeme mít zpětný přístup k naprosto všem změnám <strong>včetně smazaných řádků</strong>. Opět bych ale zdůraznil, že se rozhodně <strong>nejdená </strong>o nějaký <strong>archív smazaných položek</strong> nebo <strong>koš</strong>; logovací tabulky jsou primárně určeny pro vkládání a nic jiného. Předpokládáme zde obrovský objem dat, takže jakýkoli výpis záznamů s případným filtrováním by nám docela zavařil dráty.</p>
<h3>Rozšíření třídy Db</h3>
<pre><code class="php">private $log = false;
private $user = 0;
private $logTable;
private $tableItems;</code></pre>
<p>Ve čtyřech nových proměnných s viditelností private bude uloženo vše potřebné pro logování: ID uživatele, jména tabulek a stav zapnuto / vypnuto.</p>
<pre><code class="php">public function enableLog($user = 0){
	$this-&gt;log = true;
	if($user){
		$this-&gt;user = $user;
	}
}

public function setLogTable($logTable = null){
	if($logTable){
		$this-&gt;logTable = $logTable;
	}
}

public function setLogItemsTable($tableItems = null){
	if($tableItems){
		$this-&gt;tableItems = $tableItems;
	}
}

public function disableLog(){
	$this-&gt;log = false;
}</code></pre>
<p>Další 4 nové metody typu <strong>set </strong>budou tyto proměnné nastavovat. Metoda <em>enableLog()</em> bude volána přímo s parametrem ID uživatele, jelikož předpokládáme její zavolání někde uvnitř administračního modulu po <a href="prihlasovani-a-registrace-uzivatelu-pomoci-sessions/">přihlášení</a>. Další dvě metody umožní přenastavení výchozích jmen tabulek a nakonec zbývá už jen poslední funkce pro vypnutí. Vypínač bude mít i velice důležitou interní funkci, k tomu ale až za chvíli.</p>
<h2>Metoda insert</h2>
<p>Základní znění metody insert jsme si ukázali hned v druhém dílu, nyní si ji rozšíříme.</p>
<pre><code class="php">public function insert($table, array $items){
	try {
		if(empty($items)){
			throw new DbException('No values to insert.', var_export($items, true));
		}
		
		$keys = array();
		$vals = array();
		
		foreach ($items as $key =&gt; $t){
			if($t === 0 || $t === '0'){
				$vals[$key] = "'0'";
				$keys[]     = "`{$key}`";
			} elseif(!empty($t)){				
				$vals[$key] = "'".$this-&gt;escape($t)."'";
				$keys[]     = "`{$key}`";
			}
		}

		$qtable  = $this-&gt;replacePrefix($table);
		$final_query     = "INSERT INTO {$qtable} ( ".implode("\n,", $keys)." ) VALUES ( ".implode(",\n ", $vals)." )";
		$final_query_log = "INSERT INTO {$qtable} ( ".implode(",",   $keys)." ) VALUES ( ".implode(", ",   $vals)." ) ;";

		$insertId = $this-&gt;query($final_query, array(), false)-&gt;insertId();
		
		if($this-&gt;log){
			$this-&gt;log = false;
			$this-&gt;insert($this-&gt;logTable, array(
				'user_id'     =&gt; $this-&gt;user, 
				'table_name'  =&gt; $this-&gt;replacePrefix($table), 
				'method'      =&gt; __FUNCTION__, 
				'insert_id'   =&gt; $insertId,
				'whole_query' =&gt; $final_query_log,
			));
			$this-&gt;log = true;
		}
		
		return $insertId;
	}catch(Exception $e){
		$e-&gt;show();
	}
}</code></pre>
<p>Úprava spočívá v bloku začínajícím podmínkou<em> if($this-&gt;log)</em>. Pokud je tedy logovaní aktivní, nejdříve ho vypneme, uložíme data a pak ho znovu zapneme. Proč to? Funkce totiž volá sama sebe, takže kdybychom tak neučinili, celé by se to zacyklilo a metoda by volala sama sebe pořád dokola do nekonečna. A logovat logování fakt nechceme. Ve funkci <strong>insert </strong>logujeme až na závěr, abychom už znali <em>mysql_insert_id </em>a mohli uložit referenci na právě vytvořený záznam.</p>
<h2>Metoda update</h2>
<p>Update bude o jednu úroveň náročnější, protože zde už využijeme i druhé tabulky. Tady naopak zálohujeme záznam v jeho podobě před úpravou. Nejdříve tedy proběhne select na řádek / řádky, jenž cílíme v podmínce updatu, získané údaje uložíme a nakonec až přepíšeme.</p>
<pre><code class="php">public function update($table, $id, array $items, $col = 'id'){
	try {
		if(empty($items)){
			throw new DbException('No values to update.', var_export($items, true));
		}
		if(empty($id)){
			throw new DbException('No id.', var_export($id, true));
		}
		
		$update = array();
		foreach ($items as $key =&gt; $item){
			$update[$key] = "`{$key}` = '".$this-&gt;escape($item)."'";
		}
		
		$qtable = $this-&gt;replacePrefix($table);
		$final_query     = "UPDATE {$qtable} SET ".implode(",\n ", $update)." WHERE `{$col}` = '{$id}'";
		$final_query_log = "UPDATE {$qtable} SET ".implode(" ",    $update)." WHERE `{$col}` = '{$id}' ;";
		
		if($this-&gt;log){
			$this-&gt;log = false;	
			$_entry = $this-&gt;query("SELECT * FROM {$table} WHERE `{$col}` = '{$id}'")-&gt;assocList();
			foreach ($_entry as $_row){
				$_log_id = $this-&gt;insert($this-&gt;logTable, array(
					'user_id'     =&gt; $this-&gt;user, 
					'table_name'  =&gt; $this-&gt;replacePrefix($table), 
					'method'      =&gt; __FUNCTION__,
					'whole_query' =&gt; $final_query_log,
				));
				foreach ($_row as $_key =&gt; $_col){
					$this-&gt;insert($this-&gt;tableItems, array(
						'parent_id' =&gt; $_log_id, 
						'col_name'  =&gt; $_key, 
						'col_value' =&gt; $_col
					));
				}
			}
			$this-&gt;log = true;
		}
		
		return $this-&gt;query($final_query, array(), false)-&gt;affectedRows();
	}catch(Exception $e){
		$e-&gt;show();
	}
}</code></pre>
<p>Logování opět nejdříve vypneme před zavoláním patřičných insertů.</p>
<h2>Metoda delete</h2>
<p>Jak jsem naznačil výše, i při mazání řádků z databáze vlastně provedeme nejdřív jejich zálohu. Chování bude úplně stejné jako u metody insert.</p>
<pre><code class="php">public function delete($table, array $cond){
	try {
		if(empty($cond)){
			throw new DbException('No condition.', var_export($cond, true));
		}
		
		$c = array();
		foreach ($cond as $key =&gt; $t){
			$c[] = "`{$key}` = '".$this-&gt;escape($t)."'";
		}
		
		$qtable = $this-&gt;replacePrefix($table);
		$string = implode(' AND ', $c);
		$final_query     = "DELETE FROM {$qtable} WHERE {$string}";
		$final_query_log = "DELETE FROM {$qtable} WHERE {$string} ;";
		
		if($this-&gt;log){
			$this-&gt;log = false;
			$_entry = $this-&gt;query("SELECT * FROM {$table} WHERE {$string}")-&gt;assocList();
			foreach ($_entry as $_row){
				$_log_id = $this-&gt;insert($this-&gt;logTable, array(
					'user_id'    =&gt; $this-&gt;user, 
					'table_name' =&gt; $this-&gt;replacePrefix($table), 
					'method'     =&gt; __FUNCTION__,
					'whole_query' =&gt; $final_query,
				));
				foreach ($_row as $_key =&gt; $_col){
					$this-&gt;insert($this-&gt;tableItems, array(
						'parent_id' =&gt; $_log_id, 
						'col_name'  =&gt; $_key, 
						'col_value' =&gt; $_col
					));
				}
			}
			$this-&gt;log = true;
		}

		return $this-&gt;query($final_query, array(), false)-&gt;affectedRows();
	}catch(Exception $e){
		$e-&gt;show();
	}
}</code></pre>
<h2>Závěrem</h2>
<p>To by bylo v kostce asi vše. V příštím dílu bych seriál o databázové třídě uzavřel, abych mohl navázat slíbeným článkem o dědičnosti.</p>
<blockquote>
<p><em><em>Edit 4. 5. 2020: </em></em><br />Text se týká PHP 5. Pod PHP 7 už třída fungovat nebude, protože všechny mysql_ funkce skončí chybou.</p>
</blockquote>]]></content:encoded>
			<image>
    				<url>https://mike.treba.cz/img/2023/renewed/girl-programmer.jpg</url>
			</image>
		</item>
		
		
		<item>
			<title>Návrh databázové třídy - díl IV: Export databáze</title>
			<link>https://mike.treba.cz/navrh-databazove-tridy-dil-iv-export-databaze/</link>
			<pubDate>Sat, 3 Jul 2010 21:50:19 +1100</pubDate> 
			<comments>https://mike.treba.cz/navrh-databazove-tridy-dil-iv-export-databaze/#comments</comments>
			<dc:creator></dc:creator>
			<description><![CDATA[<p>Dnešní pokračování pohádky o databázové třídě si vezme pod lupu pár konkrétních metod. Půjde o funkce na export celé databáze s možností <strong>force download </strong>nebo postupného ukládání do souboru. O možnostech exportu databáze moc článků napsáno nebylo, navíc se nejedná o úplně triviální algoritmus s pár vnořenými cykly. V následujícím článku vám předvedu, jak problém vyřešit s elegancí.</p>]]></description>   
			<guid isPermaLink="false">https://mike.treba.cz/navrh-databazove-tridy-dil-iv-export-databaze/</guid>
			<content:encoded><![CDATA[<p>Dnešní pokračování pohádky o databázové třídě si vezme pod lupu pár konkrétních metod. Půjde o funkce na export celé databáze s možností <strong>force download </strong>nebo postupného ukládání do souboru. O možnostech exportu databáze moc článků napsáno nebylo, navíc se nejedná o úplně triviální algoritmus s pár vnořenými cykly. V následujícím článku vám předvedu, jak problém vyřešit s elegancí.</p> <p>Kostra řešení bude poměrně jednoduchá:</p>
<pre><code>SHOW TABLES

foreach ($tables) {
	SHOW CREATE TABLE
	SELECT * FROM $table
	while (FETCH_ROW) {
		foreach ($row) {
			mysql_real_escape_string($row);					
		}
		$export .= "INSERT INTO $table VALUES implode(, $row)"
	}
}
</code></pre>
<p>Nejdříve zjistíme, jaké tabulky exportujeme, následně od každé z nich získáme strukturu přes <strong>SHOW CREATE TABLE</strong>, vybereme hvězdičku, provedeme escapování a uložíme znění <strong>SQL</strong> dotazu na insert.</p>
<p>Metody na export databáze budou 2. Budou si podobné, protože v rámci zachování čitelnosti kódu není dobry nápad cpát do už tak velké funkce hromadu podmínek. První metoda uloží celý export do proměnné a nabídne soubor k uložení, druhá bude data postupně ukládat do souboru, který si pak můžeme stáhnout přes FTP klienta.</p>
<p>Před uvedením výsledné funkce si ale nejprve ukážeme pár podpůrných metod, jenž se nám budou hodit. O prováděném exportu chceme samozřejmě mít nějaké zběžné informace, které nám poskytnou následující funkce.</p>
<pre><code class="php">private function prepareBackupInfo(){
	$this-&gt;backupInfo['date_pattern'] =  @date('Y-m-d_H.i.s');
	$this-&gt;backupInfo['timestamp'] = strtr($this-&gt;backupInfo['date_pattern'], array('_' =&gt; ' ', '.' =&gt; ':'));
	$this-&gt;backupInfo['start_time'] = $this-&gt;startTime();
	$this-&gt;backupInfo['queries'] = 0;
	$this-&gt;backupInfo['written'] = 0;
}

private function appendBackupInfo(){
	$output = "";
	$output .= "\n";
	$output .= "--\n";
	$output .= "-- Queries contained: ".$this-&gt;backupInfo['queries']."\n";
	$output .= "-- Shots to file: ".$this-&gt;backupInfo['written']."\n";
	$output .= "-- Total time: ".$this-&gt;stopTime($this-&gt;backupInfo['start_time'])."\n";
	$output .= "-- Timestamp start: ".$this-&gt;backupInfo['timestamp']."\n";
	$output .= "--\n";
	return $output;
}</code></pre>
<p>Metoda <em>DB::downloadHeaders()</em> zašle potřebné hlavičky pro <strong>force download</strong>, naproti tomu <em>Db::writeToFile()</em> bude obsluhovat průběžný zápis do souboru. Nakonec uvedu ještě funkci <em>Db::addExportCondition()</em>, kterou využijeme v případě, když budeme chtít exportovat pouze některé tabulky nebo jejich části.</p>
<pre><code class="php">private function downloadHeaders($file_size, $file_type, $file_name){
	header("Pragma: public");
	header("Expires: 0");
	header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
	header("Cache-Control: private", false);
	header("Content-Transfer-Encoding: binary");
	header("Content-Type: ".$file_type);
	header("Content-Length: ".$file_size);
	header("Content-Disposition: attachment; filename=".$file_name);
}

// pouzivano na nekolika mistech v zaloze
// zapis do souboru
// @param string $file_name
// @param string $content
// @return null abych si automaticky vynulloval promennou
public function writeToFile($file_name, $content){
	$file = fopen($file_name, 'a');
	fwrite($file, $content);
	fclose($file);
	return null;
}

public function addExportCondition($table, $where = null){
	$this-&gt;exportConditions[$table] = $where; 
}</code></pre>
<p>Nyní už se můžeme pustit do exportu. Funkci uvedu pouze jednu, metodu <em>Db::dumpAndSave()</em> - tu složitější. Tělo pouze kopíruje úvodní vzor, přidaná je podmínka na kontrolu nastavení exportu (<em>konkrétní tabulky a vnitřní podmínky</em>).</p>
<pre><code class="php">public function dumpAndSave(){
	$limit = 5000000;
	
	$this-&gt;prepareBackupInfo();
	$file_name = $this-&gt;backupFolder.$this-&gt;database.'_'.$this-&gt;backupInfo['date_pattern'].'.sql';
	
	if($this-&gt;backupFolder &amp;&amp; !is_dir($this-&gt;backupFolder)){
		mkdir($this-&gt;backupFolder, '0777');
	}
	
	$_tables = array();
	$_select = $this-&gt;query('SHOW TABLES');
	
	while($t = $_select-&gt;fetch(FETCH_ROW)){
		$_tables[] = $t[0];
	}
	
	unset($_select);

	$_output = null;
	$_output = "--\n-- Database: `".$this-&gt;database."`\n--\n\n-- --------------------------------------------------------\n\n";
	$_output = $this-&gt;writeToFile($file_name, $_output);
	
	// zaloha po jedne tabulce
	foreach ($_tables as $value){
		if(!count($this-&gt;exportConditions) || (count($this-&gt;exportConditions) &amp;&amp; isset($this-&gt;exportConditions[$value]))){
		
			// show create table
			$_select_ct = $this-&gt;query("SHOW CREATE TABLE `{$value}`");
			$_row = $_select_ct-&gt;fetch(FETCH_ROW);
			
			// ukladame do souboru show create table
			$_output = null;
			$_output .= "--\n";
			$_output .= "-- `".$_row[0]."`\n";
			$_output .= "--\n\n";
			$_output .= $_row[1]." ;\n\n";
			$_output = $this-&gt;writeToFile($file_name, $_output);
			
			unset($_select_ct);
			
			&plus;&plus;$this-&gt;backupInfo['queries'];

			if(isset($this-&gt;exportConditions[$value])){
				$_select = $this-&gt;query("SELECT// FROM `{$value}` {$this-&gt;exportConditions[$value]}");
			}else{
				$_select = $this-&gt;query("SELECT// FROM `{$value}`");
			}
			
			while($t = $_select-&gt;fetch(FETCH_ROW)){
				foreach ($t as $k =&gt; $i){
					$t[$k] = "'".$this-&gt;escape($i)."'";
				}
				
				$_output .= "INSERT INTO `{$value}` VALUES (".implode(',', $t).");\n";
				&plus;&plus;$this-&gt;backupInfo['queries'];
				
				// co 5 mega, to zapis do souboru a vynulovani promenne. abych neshazoval server
				if(strlen($_output) &gt; $limit){
					$_output = $this-&gt;writeToFile($file_name, $_output);
					&plus;&plus;$this-&gt;backupInfo['written'];
				}
			}
			
			unset($_select);
			
			// pokud tu jsou nejake resty. zbavime se jich. tady uz tech 5 mega fakt testovat nebudu a rovnou to zapisu
			if($_output){
				$_output = $this-&gt;writeToFile($file_name, $_output);
				&plus;&plus;$this-&gt;backupInfo['written'];
			}
		}
	}
	
	$_output .= $this-&gt;appendBackupInfo();
	$_output = $this-&gt;writeToFile($file_name, $_output);
}</code></pre>
<p>Aplikace metod bude opět naprosto triviální. Zakomentované řádky nastaví, že se exportuje celá tabulka s kategoriemi a tabulka s produkty zařazenými do kategorie 1, 2 nebo 3.</p>
<pre><code class="php">$db = new Db;
// $db-&gt;addExportCondition('shop_categories', '');
// $db-&gt;addExportCondition('shop_products', 'WHERE category_id IN (1,2,3)');
$db-&gt;dumpAndSave();</code></pre>
<p>Znění druhé funkce si většina z vás jistě domyslí. Ostatní si budou muset počkat na zveřejnění zdrojového kódu, které je naplánováno na šestý díl. <strong>Příště</strong> si povíme něco o <strong>logování změn</strong>.</p>
<blockquote>
<p><em><em>Edit 4. 5. 2020: </em></em><br />Text se týká PHP 5. Pod PHP 7 už třída fungovat nebude, protože všechny mysql_ funkce skončí chybou.</p>
</blockquote>]]></content:encoded>
			<image>
    				<url>https://mike.treba.cz/img/2023/renewed/oop.jpg</url>
			</image>
		</item>
		
		
		<item>
			<title>Návrh databázové třídy - díl III: class Query</title>
			<link>https://mike.treba.cz/navrh-databazove-tridy-dil-iii-class-query/</link>
			<pubDate>Mon, 3 May 2010 18:56:33 +1100</pubDate> 
			<comments>https://mike.treba.cz/navrh-databazove-tridy-dil-iii-class-query/#comments</comments>
			<dc:creator></dc:creator>
			<description><![CDATA[<p>V minulém dílu seriálu o objektech v PHP5 a živém příkladu v podobě databázové třídy jsme si ukázali základní metody na typové SQL dotazů. Každá z těchto metod skončila vytvořením <strong>instance třídy Query</strong>, respektive zavoláním některé její metody. Jak to všechno funguje uvnitř třídy Query bude tématem tohoto článku. </p>]]></description>   
			<guid isPermaLink="false">https://mike.treba.cz/navrh-databazove-tridy-dil-iii-class-query/</guid>
			<content:encoded><![CDATA[<p>V minulém dílu seriálu o objektech v PHP5 a živém příkladu v podobě databázové třídy jsme si ukázali základní metody na typové SQL dotazů. Každá z těchto metod skončila vytvořením <strong>instance třídy Query</strong>, respektive zavoláním některé její metody. Jak to všechno funguje uvnitř třídy Query bude tématem tohoto článku. </p> <p>Krátce ještě zopakuji, co vše už máme: jsou to základní metody třídy Db pro volání SQL dotazů: <em>SELECT</em>, <em>UPDATE</em>, <em>INSERT</em>, <em>DELETE</em>. Hodnoty, které nás zajímají při změně či odstranění řádků tabulky už máme přímo v návratových hodnotách jednotlivých metod. Co ale ještě hotové nemáme, je zpracování klasického výběru dat z databáze.</p>
<h2>Cíle</h2>
<p>Cílem třídy Query bude kompletní obsluha SQL dotazů. Ty, jak už jsem se zmiňoval, se provedou přímo v konstruktoru. Jednotlivé metody pak už budou vracet zpracovaná data: celé řádky i sloupce, jednotlivé řádky či jediný sloupec v jediném řádku. Pokud se jí něco nebude líbit, vyhodí <strong>výjimku</strong> a dále už se o nic nestará: to bude v pravomoci třídy <strong>DbException</strong>, které se budu věnovat v pokračování. Seznam členských proměnných napoví základní vlastnosti:</p>
<pre><code class="php">final class Query implements QueryModel {

	// reference na db objekt
	// @var object	 
	private $db;
	
	// obecne nastaveni vsech instanci
	// nahrada titulku	 
	public static $titleReplace = false;

	// nyni uz info o danem SQL dotazu
	// query : string | resource | assocList | num_rows | affected_rows | insert_id
	private $stringQuery;
	private $query;
	private $assocList;
	private $numRows;
	private $affectedRows;
	private $insertId;
}</code></pre>
<p>V konstruktoru pak proběhne funkce mysql_query, která při nezdaru skončí <strong>výjimkou</strong>. Mějte ovšem na paměti, že návrh nepočítá s vytvářením instancí z jiného zdroje než jsou metody třídy Db. PHP bohužel neumí nadefinovat třídu ve třídě a tím zúžit její viditelnost, čímž pádem je potřeba tato pravidla uvést v dokumentaci.</p>
<p>Konstruktor je opravdu triviální, tam snad nic popisovat nemusím.</p>
<pre><code class="php">public function __construct(Db $db, $query){
	$this-&gt;db = $db;
	try {
		$conn = $this-&gt;db-&gt;getConnection();
		$this-&gt;query = @mysql_query($query, $conn);
		$this-&gt;stringQuery = $query;
		if(!$this-&gt;query){
			throw new DbException(null, $query);
		}
	}catch(Exception $e){
		$e-&gt;show();	
	}
}</code></pre>
<h2>mysql_num_rows, affected_rows, insert_id</h2>
<p>Metody, jenž jsem referoval v předchozím článku zajistí základní operace nad výsledkem dotazu. mysql_affected_rows a insert_id je automaticky vráceno patřičnými metodami třídy Db, pro num_rows ale musíme šáhnout na jednu z metod. Pro úplnost je zde uvedu všechny 3. Každá z nich je ošetřená proti vícenásobnému volání, abychom zbytečně neprováděli již provedené operace. Na testování používám isset ku nullovým parametrům. Míchat sem například boolean hodnoty by bylo špatně.</p>
<pre><code class="php">public function numRows(){
	if(isset($this-&gt;numRows)){
		return $this-&gt;numRows;
	}else{
		return $this-&gt;countNumRows();
	}
}

public function insertId(){
	if(!isset($this-&gt;insertId)){
		$this-&gt;insertId = mysql_insert_id();
	}
	return $this-&gt;insertId;
}

public function affectedRows(){
	if(!isset($this-&gt;affectedRows)){
		$this-&gt;affectedRows = mysql_affected_rows();
	}
	return $this-&gt;affectedRows;
}</code></pre>
<h2>Získání řádku, celé tabulky a jedné položky</h2>
<p>Abych i zde splnil cíl, který jsem si na začátku nadefinoval, přidám knihovně metody, které mi budou vracet data v přesně takovém formátu, v jakém je požaduji.</p>
<h3>mysql_result</h3>
<p>mysql_result jakožto výchozí funkce není moc šikovná, protože vrací errory, pokud SQL dotaz nevrátí žádný řádek. To ošetříme:</p>
<pre><code class="php">public function result($col = 0, $field = null) {
	if ($this-&gt;numRows()) {
		return mysql_result($this-&gt;query, $col, $field);
	} else {
		return false;
	}
}</code></pre>
<h3>Jediný řádek</h3>
<p>Často prováděná operace, proto ji zakonzervujeme do další metody. Bude vracet buď pole nebo false. Metoda fetch zastoupí všechny čtyři mysql_fetch funkce, s výchozím nastavením na assoc.</p>
<pre><code class="php">public function result($col = 0, $field = null){
	if($this-&gt;numRows()){
		return mysql_result($this-&gt;query, $col, $field);
	}else{
		return false;
	}
}

public function row($type = FETCH_ASSOC){
	$return = $this-&gt;fetch($type);
	return !empty($return) ? $return : false;
}</code></pre>
<h3>Asociativní pole z SQL dotazu</h3>
<p>Přichází na řadu nejsilnější a nejvyužívanější metoda: <strong>selectAssocList</strong>. Jejím účelem je jak jinak než převod výsledku dotazu do nějaké slušné podoby. Jednotlivé argumenty pak nahrazují další často prováděné operace, které bychom museli dělat mimo třídu během projíždění pole dalším cyklem.</p>
<p>Mám zde dvě ukázková chování: generování odkazu a přiřazení výchozí hodnoty do prázdného sloupce. V případě, že výsledek obsahuje jak sloupec <em>name</em> tak sloupec <em>title</em> (a je zapnuté nahrazování), doplní se do každé prázdné hodnoty <em>title</em> obsah sloupečku <em>name</em>.</p>
<p>V prvním parametru předáme jméno sloupce, jenž bude sloužit jako klíč. V dalších dvou pak parametry pro <strong>generování odkazů</strong>. Pokud zavoláme i s posledním parametrem, můžeme slepit více polí dohromady.</p>
<pre><code class="php">// navrat asociativniho pole
// @param string [$key] - sloupec, kt. bude klic
// @param string [$col] - sloupec, kt. bude odkaz
// @param string [$pattern] - pro generovani odkazu, ve tvaru http://www.treba.cz/kategorie/%s/	 	 	 
// @return array
public function assocList($key = null, $col = null, $pattern = null, $array = array()){
	static $checked = null;

	if(isset($this-&gt;assocList)){
		return $this-&gt;assocList;
	}else{
		$this-&gt;assocList = (array)$array;
	}

	while($t = $this-&gt;fetch()){
		if(isset($col, $pattern, $t[$col])){
			if(!isset($checked)){
				$checked = (mb_substr_count($pattern, '%s', 'utf-8') == 1);
			}
			if($checked){
				$t['href'] = sprintf($pattern, $t[$col]);
			}
		}

		// je nastaveno automaticke doplnovani titulku ze jmena
		if(self::$titleReplace &amp;&amp; array_key_exists('name', $t) &amp;&amp; array_key_exists('title', $t)){
			$t['title'] = $t['title'] ? $t['title'] : $t['name'];
		}
		
		if(isset($key, $t[$key])){
			$this-&gt;assocList[$t[$key]] = $t;
		}else{
			$this-&gt;assocList[] = $t;
		}
	}

	return $this-&gt;assocList;
}</code></pre>
<h2>Automatizace mysql_free_resultu</h2>
<p>V úvodním dílu jsem psal něco o tom, že volání funkce mysql_free_result patří k těm otravným činnostem, kterých bychom se rádi zbavili. Celou věc vyřešíme efektivně: funkci zavoláme prostě z <strong>destruktoru</strong>, takže kdykoli zanikne proměnná s objektem aktuálního SQL dotazu, provede se automaticky i mysql_free_result.</p>
<pre><code class="php">public function __unset($value = null){
	if($this-&gt;query !== false &amp;&amp; is_resource($this-&gt;query)){
		mysql_free_result($this-&gt;query);
	}
	$this-&gt;query = false;
}
	 
public function __destruct(){
	$this-&gt;__unset();
}</code></pre>
<h2>Použití</h2>
<p>Nyní už k použití. Jak jsem sliboval, zápis jednotlivých metod bude jednodušší, intuitivnější:</p>
<pre><code class="php">// vygenerování asociativního pole včetně odkazů na kategorie
$query = "SELECT * FROM ?_tabulka WHERE parent_id = '?'";
$array = $db-&gt;query($query, array($_GET['id']))-&gt;assocList('id', 'seo_url', './kategorie/%s/');

// vygenerování asociativního pole, klíče klasicky od nuly
$array = $db-&gt;query("SELECT * FROM ?_tabulka")-&gt;assocList();

// mysql_result
$result = $db-&gt;query("SELECT id FROM ?_tabulka LIMIT 1")-&gt;result();

// vytvoření zdroje &plus; projetí cyklem
$q = $db-&gt;query("SELECT * FROM ?_tabulka");
while ($item = $q-&gt;fetch()) {
	
}

// získání jediného řádku
$query = "SELECT * FROM ?_categories WHERE seo_url = '?'";
$category = $db-&gt;query($query, array($_GET['path']))-&gt;row();
</code></pre>
<div id="_mcePaste" class="mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;">V minulém díle seriálu o objektech v PHP5 a živém příkladu v podobě databázové třídy jsme si ukázali základni metody na typové SQL dotazů. Každá z těchto metod skončila vytvořením instance třídy Query, respektive zavoláním některé její metody. Jak to všechno funguje uvnitř třídy Query bude tématem tohoto článku.<br /><br />Krátce ještě zopakuji, co vše už máme: jsou to základní metody třídy Db pro volání SQL dotazů: SELECT, UPDATE, INSERT, DELETE. Hodnoty, které nás zajímají při změně či odstranění řádků tabulky už máme přímo v návratových hodnotách jednotlivých metod. Co ale ještě hotové nemáme, je zpracování klasického výběru dat z databáze.<br />Cíle<br /><br />Cílem třídy Query bude kompletní obsluha SQL dotazů. Ty, jak už jsem se zmiňoval, se provedou přímo v konstruktoru. Jednotlivé metody pak už budou vracet zpracovaná data: celé řádky i sloupce, jednotlivé řádky či jediný sloupec v jediném řádku. Pokud se jí něco nebude líbít, vyhodí vyjímku a dále už se o nic nestará: to bude v pravomoci třídy DbException, které se budu věnovat v pokračování. Seznam členských proměnných napoví základní vlastnosti:<br />final class Query implements QueryModel { <br /> <br /> /** <br /> * reference na db objekt <br /> * @var object <br /> **/ <br /> private $db; <br /> <br /> /** <br /> * obecné nastavení všech instancí <br /> * náhrada titulku <br /> **/ <br /> public static $titleReplace = false; <br /> <br /> /** <br /> * nyní už info o daném SQL dotazu <br /> * query : string | resource | assocList | num_rows | affected_rows | insert_id <br /> **/ <br /> private $stringQuery; <br /> <br /> private $query; <br /> <br /> private $assocList; <br /> <br /> private $numRows; <br /> <br /> private $affectedRows; <br /> <br /> private $insertId; <br />} <br /><br />V konstruktoru pak proběhne funkce mysql_query, která při nezdaru skončí vyjímkou. Mějte ovšem na paměti, že návrh nepočítá s vytvářením instancí z jiného zdroje než jsou metody třídy Db. PHP bohužel neumí nadefinovat třídu ve třídě a tím zúžit její vyditelnost, čímž pádem je potřeba tato pravidla uvést v dokumentaci.<br /><br />Konstruktor je opravdu triviální, tam snad nic popisovat nemusím.<br />/** <br /> * konstruktor <br /> * @param Db object $db <br /> * @param string $query <br /> **/ <br />public function __construct(Db $db, $query) { <br /> $this-&gt;db = $db; <br /> <br /> try { <br /> $conn = $this-&gt;db-&gt;getConnection(); <br /> $this-&gt;query = @mysql_query($query, $conn); <br /> $this-&gt;stringQuery = $query; <br /> <br /> if (!$this-&gt;query) { <br /> throw new DbException(null, $query); <br /> } <br /> } catch (Exception $e) { <br /> $e-&gt;show(); <br /> } <br />}<br />mysql_num_rows, <br />affected_rows, <br />insert_id<br /><br />Metody, jenž jsem referoval v předchozím článku zajistí základní operace nad výsledkem dotazu. mysql_affected_rows a insert_id je automaticky vráceno patřičnými metodami třídy Db, pro num_rows ale musíme šáhnout na jednu z metod. Pro úplnost je zde uvedu všechny 3. Každá z nich je ošetřená proti vícenásobnému volání, abychom zbytečně neprováděli již provedené operace. Na testování používám isset ku nullovým parametrům. Míchat sem například boolen hodnoty by bylo špatně.<br />public function numRows() { <br /> if (isset($this-&gt;numRows)) { <br /> return $this-&gt;numRows; <br /> } else { <br /> return $this-&gt;countNumRows(); <br /> } <br />} <br /> <br />public function insertId() { <br /> if (!isset($this-&gt;insertId)) { <br /> $this-&gt;insertId = mysql_insert_id(); <br /> } <br /> return $this-&gt;insertId; <br />} <br /> <br />public function affectedRows() { <br /> if (!isset($this-&gt;affectedRows)) { <br /> $this-&gt;affectedRows = mysql_affected_rows(); <br /> } <br /> return $this-&gt;affectedRows; <br />}<br />Získání <br />řádku, <br />celé <br />tabulky <br />a <br />jedné <br />položky<br /><br />Abych i zde splnil cíl, který jsem si na začátku nadefinoval, přidám knihovně metody, které mi budou vracet data přesně v takovém formátu, v jakém je požaduji.<br />mysql_result<br /><br />Mysql_result jakožto výchozí funkce není moc šikovná, protože vrací errory, pokud SQL dotaz nevrátí žádný řádek. To ošetříme:<br />public function result($col = 0, $field = null) { <br /> if ($this-&gt;numRows()) { <br /> return mysql_result($this-&gt;query, $col, $field); <br /> } else { <br /> return false; <br /> } <br />}<br />Jediný <br />řádek<br /><br />Často prováděná operace, proto ji zakonzervujeme do další metody. Bude vracet buď pole nebo false. Metoda fetch zastoupí všechny čtyři mysql_fetch funkce, s výchozím nastavením na assoc.<br />public function fetch($type = FETCH_ASSOC) { <br /> $function = 'mysql_fetch_'.$type; <br /> return $function($this-&gt;query); <br />} <br /> <br />public function row() { <br /> $return = $this-&gt;fetch(); <br /> return !empty($return) ? $return : false; <br />} <br />Asociativní <br />pole <br />z <br />SQL <br />dotazu<br /><br />Přichází na řadu nejsilnější a nejvyužívanější metoda: selectAssocList. Jejím účelem je jak jinak, než převod výsledku dotazu do nějaké slušné podoby. Jednotlivé argumenty pak nahrazují další často prováděné oprace, které bychom museli dělat mimo třídu během projíždění pole dalším cyklem.<br /><br />Mám zde dvě ukázková chování: generování odkazu a přiřazení výchozí hodnoty do prázdného sloupce. V případě, že výsledek obsahuje jak sloupec name tak sloupec title (a je zapnuté nahrazování), doplní se do každé každé prázdné hodnoty title obsah sloupečku name.<br /><br />V prvním parametru předáme jméno sloupce, jenž bude sloužit jako klíč. V dalších dvou pak parametry pro generování odkazů. Pokud zavoláme i s posledním parametrem, můžeme slepit více polí dohromady.<br />/** <br /> * návrat asociativního pole <br /> * @param string [$key] - sloupec, kt. bude klíč * @param string [$col] - sloupec, kt. bude odkaz <br /> * @param string [$pattern] - pro generování odkazu, ve tvaru http://www.treba.cz/kategorie/%s/ <br /> * @return array <br /> **/ <br />public function assocList($key = null, $col = null, $pattern = null, $array = array()) { <br /> static $checked = null; <br /> <br /> if (isset($this-&gt;assocList)) { <br /> return $this-&gt;assocList; <br /> } else { <br /> $this-&gt;assocList = (array)$array; <br /> } <br /> <br /> while ($t = $this-&gt;fetch()) { <br /> if (isset($col, $pattern, $t[$col])) { <br /> <br /> // pouze pro to, aby mb_substr_count proběhl jen jednou <br /> if (!isset($checked)) { <br /> if (mb_substr_count($pattern, '%s', 'utf-8') == 1) { <br /> $checked = true; <br /> } else { <br /> $checked = false; <br /> } <br /> } <br /> <br /> if ($checked) { <br /> $t['href'] = sprintf($pattern, $t[$col]); <br /> } <br /> } <br /> <br /> // je nastaveno automatické doplňování titulku ze jména <br /> if (self::$titleReplace &amp;&amp; array_key_exists('name', $t) &amp;&amp; array_key_exists('title', $t)) { <br /> $t['title'] = $t['title'] ? $t['title'] : $t['name']; <br /> } <br /> <br /> if (isset($key, $t[$key])) { <br /> $this-&gt;assocList[$t[$key]] = $t; <br /> } else { <br /> $this-&gt;assocList[] = $t; <br /> } <br /> } <br /> <br /> return $this-&gt;assocList; <br />} <br />Automatizace <br />mysql_free_resultu<br /><br />V úvodním díle jsem psal něco o tom, že volání funkce mysql_free_result patří k těm otravným činostem, kterých bychom se rádi zbavili. Celou věc vyřešíme efektivně: funkci zavoláme prostě z destruktoru, takže kdykoli zanikne proměnná s objektem aktuálního SQL dotazu, provede se automaticky i mysql_free_result.<br />public function __unset($value = null) { <br /> if ($this-&gt;query !== false &amp;&amp; is_resource($this-&gt;query)) { <br /> mysql_free_result($this-&gt;query); <br /> } <br /> $this-&gt;query = false; <br />} <br /> <br />public function __destruct() { <br /> $this-&gt;__unset(); <br />} <br />Použití<br /><br />Nyní už k použití. Jak jsem sliboval, zápis jednotlivých metod bude jednodušší, intuitivnější:<br />// vygenerování asociativního pole včetně odkazů na kategorie <br />$query = "SELECT * FROM ?_tabulka WHERE parent_id = '?'"; <br />$array = $db-&gt;query($query, array($_GET['id']))-&gt;assocList('id', 'seo_url', './kategorie/%s/'); <br /> <br />// vygenerování asociativního pole, klíče klasicky od nuly <br />$array = $db-&gt;query("SELECT * FROM ?_tabulka")-&gt;assocList(); <br /> <br />// mysql_result <br />$result = $db-&gt;query("SELECT id FROM ?_tabulka LIMIT 1")-&gt;result(); <br /> <br />// tytvoření zdroje &plus; projetí cyklem <br />$q = $db-&gt;query("SELECT * FROM ?_tabulka"); <br />while ($item = $q-&gt;fetch()) { <br /> <br />} <br /> <br />// získlání jediného řádku <br />$query = "SELECT * FROM ?_categories WHERE seo_url = '?'"; <br />$category = $db-&gt;query($query, array($_GET['path']))-&gt;row();</div>
<blockquote>
<p><em>Edit 4. 5. 2020: </em><br />Text se týká PHP 5. Pod PHP 7 už třída fungovat nebude, protože všechny mysql_ funkce skončí chybou.</p>
</blockquote>]]></content:encoded>
			<image>
    				<url>https://mike.treba.cz/img/2023/renewed/code.jpg</url>
			</image>
		</item>
		
		
		<item>
			<title>Návrh databázové třídy - díl II: Základní metody</title>
			<link>https://mike.treba.cz/navrh-databazove-tridy-dil-ii-zakladni-metody/</link>
			<pubDate>Tue, 13 Apr 2010 21:29:21 +1100</pubDate> 
			<comments>https://mike.treba.cz/navrh-databazove-tridy-dil-ii-zakladni-metody/#comments</comments>
			<dc:creator></dc:creator>
			<description><![CDATA[<p>V úvodu svého seriálu o <strong>databázi</strong> a <strong>objektech</strong> jsem nakousnul základní témata, kterými bude směřovat postup realizace. V pokračování uvedu elementární metody na nečastější typy SQL dotazů. </p>]]></description>   
			<guid isPermaLink="false">https://mike.treba.cz/navrh-databazove-tridy-dil-ii-zakladni-metody/</guid>
			<content:encoded><![CDATA[<p>V úvodu svého seriálu o <strong>databázi</strong> a <strong>objektech</strong> jsem nakousnul základní témata, kterými bude směřovat postup realizace. V pokračování uvedu elementární metody na nečastější typy SQL dotazů. </p> <p>Naším cílem bude již zmiňované omezení otravných operací spolu s maximálním zjednodušení zápisů konkrétních typů dotazů. Nepodstatný výčet členských proměnných přeskočím a přejdu rovnou ke <strong>konstruktoru třídy</strong>.</p>
<h2>Obecné nastavení</h2>
<p>Než se vůbec dostaneme k samotné práci s daty, je potřeba nastavit potřebné parametry a provést spojení. U jednotlivých knihoven s oblibou využívám kombinaci nastavení přes globální proměnné a metody typu <strong>set</strong>. Princip je takový, že nastavení, které je společné pro všechny potencionální instance dané třídy nechám v globální proměnných, naproti tomu specifická nastavení uložím do členských proměnných až po vytvoření instance. V našem případě ale zůstanu pouze u globálních proměnných, a to z důvodu bezpečnosti, jelikož chceme používat <strong>výjimky v PHP5</strong> a nechceme riskovat nechtěný výpis hesla k databázi.</p>
<p>Parametrem v tomto případě bude klíč ke globálnímu poli, kde bude veškeré nastavení uloženo. Nastavím všechny 4 potřebné údaje, kódování, prefix tabulek a znění SQL dotazu pro <strong>porovnávání</strong>.</p>
<pre><code class="php">public function __construct($key = 'db'){
	global $config;
	
	try {
		if(!empty($config[$key]['connected'])){
			throw new DbException('Connection from $config['.$key.'] already exists.');
		}
		
		if(!isset($config[$key]['host'], $config[$key]['user'], $config[$key]['pass'], $config[$key]['name'])){
			throw new DbException('One of global vars $config['.$key.'][host] // user | pass | name is undefined.');
		}
		
		$this-&gt;database = $config[$key]['name'];
		
		$this-&gt;connection = @mysql_connect($config[$key]['host'], $config[$key]['user'], $config[$key]['pass']);
		if(!$this-&gt;connection){
			throw new DbException();
		}
		
		$this-&gt;selectedDb = @mysql_select_db($this-&gt;database, $this-&gt;connection);
		if(!$this-&gt;selectedDb){
			throw new DbException();
		}
		
		$this-&gt;magicQuotes = (get_magic_quotes_gpc() ? true : false);
		$this-&gt;setNames = (!empty($config[$key]['charset']) ? $config[$key]['charset'] : 'SET NAMES utf8');
		$this-&gt;prefix = (!empty($config[$key]['prefix']) ? $config[$key]['prefix'] : '');
		
		// pokud tabulka obsahuje jak klic [name] tak klic [title]
		// pokud je title prazdny, priradi se hodnota sloupce name
		// kvuli SEO : name pro &lt;h1&gt; title pro &lt;title&gt; aby mohly byt odlisne
		if(isset($config[$key]['title_replace'])){
			Query::$titleReplace = (bool)$config[$key]['title_replace'];
		}
		
		$this-&gt;query($this-&gt;setNames);
		
	}catch(Exception $e){
		$e-&gt;show();
	}
	
	// reset aby se v nejakem dumpu nahodou neobjevilo heslo k databazi
	$config[$key]['host'] = 'true';
	$config[$key]['user'] = 'true';
	$config[$key]['pass'] = 'true';
	$config[$key]['name'] = 'true';
	$config[$key]['connected'] = true;
}</code></pre>
<p>Od pohledu je asi jasné, co všechno <strong>konstruktor</strong> řeší. O některých funkcích zde ale vůbec mluvit nebudu, a ušetřený čas raději věnuji podrobnějšímu popisu, proč tohle všechno děláme: automatizace, ušetření práce. Jen ještě připomenu, že řádek, ve kterém se zjišťuje aktuální nastavení <strong>magických uvozovek</strong> si bere za úkol kompletně se postarat o tuto problematiku. Nás už následně nebude zajímat žádné slashování či escapování.</p>
<h2>Dotazy typu SELECT</h2>
<p>Právě metoda Db::query() bude zajišťovat veškeré dotazy na databázi. Provede náhradu prefixu tabulek, ošetří veškerý vstup proti MySQL Injection a čistá data pošle třídě Query. Mimo to i změří kolik času SQL dotaz sebral. Automatické ošetření vstupu provedeme pomocí takzvaných placeholderů, které budou ve funkci nahrazeny.</p>
<pre><code class="php">public function query($query, $pholders = null, $replacePrefix = true){
	$time = $this-&gt;startTime();
	if($replacePrefix){
		$query = $this-&gt;replacePrefix($query);
	}
	
	if(!empty($pholders)){
		$q = explode('?', $query);
		$count = count($pholders);
		$query = '';
		for ($i = 0; $i &lt; $count; $i&plus;&plus;){
			$query .= $q[$i].$this-&gt;escape($pholders[$i]);
		} 
		$query .= $q[$i];
	}

	$result = new Query($this, $query);
	$this-&gt;queries[] = $result.'; '.$this-&gt;stopTime($time);
	return $result;
}</code></pre>
<p>Díky tomu dosáhneme naprosto jednoduchého zápisu:</p>
<pre><code class="php">$q = $db-&gt;query("SELECT * FROM ?_tabulka WHERE id = '?' AND category_id = '?'", array($id, $category_id));
</code></pre>
<p>Nyní k vysvětlení otazníků: ?_ znamená prefix tabulek. Význam začíná mít právě ve chvíli, kdy nemáme možnost vytvoření nové databáze pro projekt na stejném systému. A samotný otazník už je placeholder, obdobně jako například v PDO. Metody <em>startTime()</em>, <em>stopTime()</em> a <em>replacePrefix()</em> doufám popisovat nemusím.</p>
<h2>Trojice funkcí pro dotazy typu <em>INSERT</em>, <em>UPDATE</em> a <em>DELETE</em></h2>
<p>Jak napoví nadpis, funkce si budou něčím podobné. V tomto případě totiž už nebudeme psát celé znění dotazu, ale pouze předáme jméno tabulky, pole s daty a případnou podmínku.</p>
<h3>Vložení nového řádku</h3>
<p>Jak jsem slíbil, tak učiním. Nejjednodušší z nadepsaných funkcí, metoda Db::insert() bere 2 parametry: jméno tabulky &plus; pole s hodnotami a vrací ID vloženého záznamu. Je samozřejmě nutné, aby seděly klíče a jména sloupců vůči tabulce.</p>
<pre><code class="php">public function insert($table, array $items){
	try {
		if(empty($items)){
			throw new DbException('No values to insert.', var_export($items, true));
		}
		
		$keys = array();
		$vals = array();
		
		foreach ($items as $key =&gt; $t){
			if($t === 0 || $t === '0'){
				$vals[$key] = "'0'";
				$keys[]     = "`{$key}`";
			}elseif(!empty($t)){				
				$vals[$key] = "'".$this-&gt;escape($t)."'";
				$keys[]     = "`{$key}`";
			}
		}

		$qtable  = $this-&gt;replacePrefix($table);
		$final_query     = "INSERT INTO {$qtable} ( ".implode("\n,", $keys)." ) VALUES ( ".implode(",\n ", $vals)." )";
		
		$insertId = $this-&gt;query($final_query, array(), false)-&gt;insertId();
		
		return $insertId;
	}catch(Exception $e){
		$e-&gt;show();
	}
}</code></pre>
<h3>UPDATE</h3>
<p>Nejčastěji prováděná aktualizace záznamu je jeden řádek podmíněný jedním ID. Právě tuto akci si <strong>automatizujeme</strong>. Výsledek bude obdobná funkce akorát s odlišně vygenerovaným řetězcem.</p>
<pre><code class="php">public function update($table, $id, array $items, $col = 'id'){
	try {
		if(empty($items)){
			throw new DbException('No values to update.', var_export($items, true));
		}
		if(empty($id)){
			throw new DbException('No id.', var_export($id, true));
		}
		
		$update = array();
		foreach ($items as $key =&gt; $item){
			$update[$key] = "`{$key}` = '".$this-&gt;escape($item)."'";
		}
		
		$qtable = $this-&gt;replacePrefix($table);
		$final_query     = "UPDATE {$qtable} SET ".implode(",\n ", $update)." WHERE `{$col}` = '{$id}'";
		
		return $this-&gt;query($final_query, array(), false)-&gt;affectedRows();
	}catch(Exception $e){
		$e-&gt;show();
	}
}</code></pre>
<h3>DELETE</h3>
<p>Mazání přecijen provádíme méně často než vkládání nových řádků a jejich editace, a tak by nám do základu měla stačit metoda, která bude mazat řádek / řádky pouze s porovnáním v podmínce. Metoda vezme dva argumenty: jméno tabulky a pole s podmínkou.</p>
<pre><code class="php">public function delete($table, array $cond){
	try {
		if(empty($cond)){
			throw new DbException('No condition.', var_export($cond, true));
		}
		
		$c = array();
		foreach ($cond as $key =&gt; $t){
			$c[] = "`{$key}` = '".$this-&gt;escape($t)."'";
		}
		
		$qtable = $this-&gt;replacePrefix($table);
		$string = implode(' AND ', $c);
		$final_query     = "DELETE FROM {$qtable} WHERE {$string}";

		return $this-&gt;query($final_query, array(), false)-&gt;affectedRows();
	}catch(Exception $e){
		$e-&gt;show();
	}
}</code></pre>
<h2>Přínos</h2>
<p>Ač se výše uvedené metody mohou zdát na první pohled zbytečně složité, splňují na 100% vše, co od nich očekáváme a navíc zcela správně implementují princip knihovny: složité vnitřní operace s <strong>vlastním reportem chyb</strong> a jejich jednoduchá aplikace. Po vyladění těchto metod (<em>což už samozřejmě vyladěné mám</em>) nás tělo funkce přestane zajímat a budeme se starat pouze o vstup a návratové hodnoty. A pokud si honem nevzpomeneme, jaké že to argumenty musíme předat, mrkneme na <strong>interface</strong>.</p>
<p>Pro náročnější dotazy už je samozřejmě třeba volat klasickou metodu Db::query(), ale existují věci, které prostě zobecnit nelze.</p>
<h3>Ukázka</h3>
<pre><code class="php">$insert_id = $db-&gt;insert('?_categories', array(
	'name' =&gt; $_POST['name'],
	'text' =&gt; $_POST['text'],
));

$db-&gt;update('?_categories', $_GET['id'], array(
	'name' =&gt; $_POST['name'],
	'text' =&gt; $_POST['text'],
));

$deleted = $db-&gt;delete('?_articles', array(
	'parent_id' =&gt; 1,
));</code></pre>
<blockquote>
<p><em><em>Edit 4. 5. 2020: </em></em><br />Text se týká PHP 5. Pod PHP 7 už třída fungovat nebude, protože všechny mysql_ funkce skončí chybou.</p>
</blockquote>]]></content:encoded>
			<image>
    				<url>https://mike.treba.cz/img/2023/renewed/girl-programmer.jpg</url>
			</image>
		</item>
		
		
		<item>
			<title>Návrh databázové třídy - díl I: Úvod, cíle</title>
			<link>https://mike.treba.cz/objekty-v-php5-navrh-databazove-tridy-dil-i-uvod-cile/</link>
			<pubDate>Mon, 8 Mar 2010 20:05:32 +1100</pubDate> 
			<comments>https://mike.treba.cz/objekty-v-php5-navrh-databazove-tridy-dil-i-uvod-cile/#comments</comments>
			<dc:creator></dc:creator>
			<description><![CDATA[<p>Jaro už je pomalu za dveřmi, je nejvyšší čas pustit se do objektů. Jako učební pomůcku jsem zvolil právě <strong>databázový layer</strong>, jelikož na jeho návrhu a tvorbě se dá mnohé ukázat i vysvětlit, o využití v praxi nemluvě. </p>]]></description>   
			<guid isPermaLink="false">https://mike.treba.cz/objekty-v-php5-navrh-databazove-tridy-dil-i-uvod-cile/</guid>
			<content:encoded><![CDATA[<p>Jaro už je pomalu za dveřmi, je nejvyšší čas pustit se do objektů. Jako učební pomůcku jsem zvolil právě <strong>databázový layer</strong>, jelikož na jeho návrhu a tvorbě se dá mnohé ukázat i vysvětlit, o využití v praxi nemluvě. </p> <p>Sám jsem začínal zprvu jednoduchým podnětem: potřebou monitorovat a sčítat provedené SQL dotazy. Skutečný život se ale následně ukázal mnohem složitější, a tak bylo potřeba napsat pokročilou knihovnu, která toho co nejvíce udělá za mě. Proto nyní otevírám miniseriál tak trochu o <strong>objektech </strong>a tak trochu o<strong> návrhových vzorech</strong>.</p>
<h2>Zadání</h2>
<p>Každý úkol by měl mít své zadání, takže nejdříve si specifikujeme body, které se pokusíme splnit. Úkolem třídy bude komunikace s <strong>MySQL databází</strong>, podchycení nejčastěji prováděných akcí za pomocí metod, které mám kompletně nahradí <em>mysql_</em> funkce v jazyce PHP. To vše samozřejmě za využití výhod, které nám <strong>objektově orientované programování</strong> v PHP5 nabízí.</p>
<h3>Jaké jsou tedy cíle?</h3>
<ul>
<li>Automatizovat otravných, přesto nutných operací.</li>
<li>Docílení sympatičtějšího zápisu opakovaně volaných funkcí.</li>
<li>Příprava platformy pro snadné připojení k více databázím. Jedna instance bude rovna jednomu otevřenému spojení.</li>
<li>Tvorba prostředí pro ladění SQL dotazů.</li>
<li>A v neposlední řadě se také něco naučit a zlepšit si abstraktní myšlení :o)</li>
</ul>
<h2>Prvotní návrh</h2>
<p>Rozhraní, které je vlastně takový <em>to do list</em>, nám definuje všechny veřejné metody, které má výsledná třída obsahovat. Většinu těch soukromých zatím vůbec zmiňovat nebudu, jelikož po jejich implementaci nás stejně přestanou zajímat. V prvním kroku tedy definujeme tři <strong>rozhraní</strong>, jedno pro každou třídu, která bude ve výsledku existovat.</p>
<h3>Rozhraní pro class Db</h3>
<p>Kostra databázové třídy napoví, co od ní budeme očekáváme. Jejím úkolem bude mimo jiné uchovávat <strong>spojení s databází</strong>, ošetřovat veškerý vstup před předáním další třídě a zautomatizovat zdlouhavé psaní dotazů typu <em>INSERT</em>, <em>UPDATE</em> a <em>DELETE</em>. Nakonec ji také rozšíříme o <strong>export tabulek</strong>.</p>
<pre><code class="php">interface DbModel 
{
	public function __construct($key = 'db');
	
	public function query($query, $pholders = null);
	public function select($query, $pholders = null, $replacePrefix = true);
	public function insert($table, array $items);
	public function update($table, $id, array $items, $col = 'id');
	public function delete($table, array $cond);
	
	public function replacePrefix($string); 
	public function escape($string);
	public function getUniqueColumns($table);
	public function columnExists($table, $col);
	public function tableExists($table);
	
	public function startTime();
	public function stopTime($start, $format = 6);
	
	public function dumpAndSave();
	public function dumpAndDownload();
	public function writeToFile($file_name, $content);
	public function addExportCondition($table, $where = null);
	
	public function getConnection();
	public function getQueries();
	public function numQueries();
	public function getMagicQuotes();
	
	public function setPrefixChar($char = null);
	public function setBackupFolder($folder); 
	
// 	public function enableLog($user_id = 0);
// 	public function setLogTable($logTable = null);
// 	public function setLogItemsTable($tableItems = null);
// 	public function disableLog();
	
	public function __destruct();
}</code></pre>
<h3>class Query</h3>
<p>Syrové informace, které dostane třída Db budou po zpracování odeslány <strong>konstruktoru</strong> třídy <strong>Query</strong>. Půjde o <strong>znění SQL dotazu</strong> plus samozřejmě referenci instanci Db. Jediný konstruktor bude tedy data vkládat, všechny ostatní metody je budou vracet. Rozhraní opět napoví, jaké chování od objektu vyžadujeme.</p>
<pre><code class="php">interface QueryModel 
{
	public function __construct(Db $db, $query);
	public function assocList($key = null, $col = null, $pattern = null, $array = array());
	public function result($col = 0, $field = null);
	public function row();
	public function numRows();
	public function insertId();
	public function affectedRows();
	public function countNumRows();
	public function fetch($type = FETCH_ASSOC);
	public function __toString();
	public function __unset($value = null);
	public function __destruct();
}
</code></pre>
<h3>class DbException</h3>
<p>Class DbException bude pouze rozšíření třídy <strong>Exception</strong> o jednu metodu, které se postará o smysluplný výpis informací o vzniklé chybě. Do konstruktoru budeme předávat vlastní chybové hlášení plus znění ztroskotaného SQL dotazu.</p>
<pre><code class="php">interface DbExceptionModel 
{
	public function __construct($msg = null, $errString = null);
	public function show();
}</code></pre>
<p>To by pro úvod zatím stačilo, v pokračování začneme rovnou spojením s databází v metodě <code>Db::__construct</code> a hlavními funkcemi, které využijeme pro výběr, vkládání a mazání dat.</p>
<blockquote>
<p>Edit 4. 5. 2020: <br />Text se týká PHP 5. Pod PHP 7 už třída fungovat nebude, protože všechny mysql_ funkce skončí chybou. </p>
</blockquote>]]></content:encoded>
			<image>
    				<url>https://mike.treba.cz/img/2023/renewed/oop.jpg</url>
			</image>
		</item>
		
		
		<item>
			<title>PHP: Vlastní error handler</title>
			<link>https://mike.treba.cz/php-vlastni-handler-warning-fatal-error/</link>
			<pubDate>Sun, 14 Feb 2010 21:02:57 +1100</pubDate> 
			<comments>https://mike.treba.cz/php-vlastni-handler-warning-fatal-error/#comments</comments>
			<dc:creator></dc:creator>
			<description><![CDATA[<p>Je tomu již více jak tři roky, kdy můj internetový deníček střídavě funguje a nefunguje. Je proto nejvyšší čas rozdělit články v kategorii PHP na dvě další dle obtížnosti. Proto bych rád kategorii <a href="kategorie/php-pro-pokrocile/">PHP pro pokročilé</a> otevřel článkem, ve kterém ukážu jak na chyby se vším všudy. </p>]]></description>   
			<guid isPermaLink="false">https://mike.treba.cz/php-vlastni-handler-warning-fatal-error/</guid>
			<content:encoded><![CDATA[<p>Je tomu již více jak tři roky, kdy můj internetový deníček střídavě funguje a nefunguje. Je proto nejvyšší čas rozdělit články v kategorii PHP na dvě další dle obtížnosti. Proto bych rád kategorii <a href="kategorie/php-pro-pokrocile/">PHP pro pokročilé</a> otevřel článkem, ve kterém ukážu jak na chyby se vším všudy. </p> <p>Jelikož už se se standardními zprávami typu "<em>Fatal error: Call to undefined function...</em>" nespokojíme, je potřeba si rozšířit znalosti o některé funkce a předdefinované třídy, jenž nám ulehčí <strong>vývoj aplikací</strong>.</p>
<blockquote>
<p>Prosím berte v potaz, že článek je z roku <strong>2010</strong>. Hodně se za tu dobu změnilo, a tak návod nemusí odpovídat dnešním standardům. Ukázka ale funguje a můžete ji využít jako první krok k lepšímu porozumnění PHP.</p>
</blockquote>
<p>Chyby, které mohou v aplikaci nastat bych rád rozdělil na 4 skupiny: podrobně se budu věnovat prvním dvěma z nich, o těch dalších dvou třeba v pokračování. Půjde o následující skupiny:</p>
<ol>
<li><strong>Warning, notice, strict</strong></li>
<li><strong>Fatal a parse error</strong></li>
<li>Database error</li>
<li>Exceptions: uměle vyvolané výjimky</li>
</ol>
<h2>Warning, notice, strict</h2>
<p>Návod jak si "vytunit" <strong>chybová hlášení</strong> typu <strong>warning</strong> a <strong>notice</strong> je přímo uvedené i v oficiálním manuálu. Co už se ale nedočtete, je, že na fatální a syntaktické chyby je toto řešení krátké. Pokud totiž taková chyba vznikne, běh skriptu se okamžitě ukončí a server rozhodně nemá náladu na volání nějakých námi definovaných funkci.</p>
<p>Zpracování těchto chyb totiž spočívá v deklaraci vlastní funkce, kterou pomocí <strong>set_error_handler</strong> nastavíme jako primární ovladač chyb. Na co je dále nutné myslet je, že při použití set_error_handler přestane bez výjimky fungovat <strong>zavináčový hack</strong>. Ač od toho bývá spousta začátečníků odrazována, jedná se o výbornou věc... Tomu se ale dnes věnovat nebudu.</p>
<p>V následující ukázce kódu zpracujeme všechny chyby typu warning, notice, strict a jim podobné. Ukázka předpokládá, že všechna chybová hlášení jsou zapnutá jak přes <em>display_errors</em>, tak přes <em>error_reporting</em>.</p>
<pre><code class="php">function user_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
	static $type = array(
		E_ERROR =&gt; 'Fatal Error',
		E_PARSE =&gt; 'Parse Error',
		E_RECOVERABLE_ERROR =&gt; 'Recoverable Error',
		E_WARNING =&gt; 'Warning',
		E_NOTICE =&gt; 'Notice',
		E_CORE_ERROR =&gt; 'Core Error',
		E_CORE_WARNING =&gt; 'Core Warning',
		E_COMPILE_ERROR =&gt; 'Compile Error',
		E_COMPILE_WARNING =&gt; 'Compile Warning',
		E_USER_ERROR =&gt; 'User Error',
		E_USER_WARNING =&gt; 'User Warning',
		E_USER_NOTICE =&gt; 'User Notice',
		E_STRICT =&gt; 'Runtime Notice',
		E_ALL =&gt; 'All',
	);

	/**
	 * ošetření zavináčového hacku
	 * když ho použijeme, ani email nedojde
	 **/
	if (!ini_get('error_reporting')) {
		return true;
	}

	$e = new Exception();
	
	$msg_begin = '&lt;b&gt;' . $type[$errno] . '&lt;/b&gt;: &lt;i&gt;' . $errstr . '&lt;/i&gt; in ' . $errfile . ':' . $errline . "\n";
	
	$msg_email = $msg_begin . "\nTrace:\n" . $e-&gt;getTraceAsString();
	$msg_print = $msg_begin . $e-&gt;getTraceAsString();
	
	$notif = user_error_notification(strip_tags($msg_email), $type[$errno]);

	if (!empty($GLOBALS['config']['error']['enable'])) {
		echo '&lt;pre style="background:#fff;color:red;padding:5px;"&gt;';
		echo $msg_print . '&lt;br /&gt;' . $notif;
		echo '&lt;/pre&gt;';
	}
}

set_error_handler('user_error_handler'); </code></pre>
<p>Všimněte si podmínky uprostřed, která testuje ini_get('error_reporting'). Jedná o ošetření již zmiňovaného zavináčového hacku, aby se  choval stejně jako před tím. Dále je vytvořena instance třídy <strong>Exception</strong>. Ta se postará o naši kompletní informovanost, co všechno chybě předcházelo.</p>
<p>Před vytištěním znění chyby ještě zavoláme ukázkovou funkci (kterou jsem pro příklad pojmenoval <strong>user_error_notification</strong>. Ta pošle e-mail, kde a kdy k chybě došlo. Vrátit může například zprávu, zda-li se e-mail povedlo či nepovedlo odeslat.</p>
<p>Ve finále podmiňuji globální proměnnou  <code>$config['error']['enable'].</code> V té je nastavena boolean hodnota, díky které se chyba zobrazí či nezobrazí. To kvůli přechodu z vývojového na ostré prostředí, abychom návštěvníkům zbytečně nezobrazovali nesrozumitelná hlášení. Žádné vypínání error_reporting, ale efektivní řešení, kdy my jakožto vývojáři jsme informovaní o chybě, ale návštěvník nikoli.</p>
<h2>Fatal &amp; parse error</h2>
<p>Jak jsem již zmiňoval, na tyto chyby je potřeba úplně jiným metrem. Existují dvě krásné direktivy, které pro náš účel použijeme. Půjde o parametry <em>error_prepend_string</em> a <em>error_append_string</em>, které jsou argumenty jak jinak než funkce <em>ini_set</em>. Do každé z nich vložíme část HTML kódu, který vytvoří pěkně nastylovanou stránku. Pomocí JavaScriptu skryjeme skutečné hlášení a uživateli zobrazíme pouze omluvnou zprávu.</p>
<p>E-mailové notifikace také nebudou obtížné: stačí přidat <strong>Ajaxový request</strong> na skript, který z GET proměnné vytáhne znění chyby a z HTTP_REFERERA url, kde se chyba stala. Použití třídy Exception zde samozřejmě odpadá.</p>
<pre><code class="php">
$err_begin_pattern = &lt;&lt;&lt;HTML
&lt;!DOCTYPE html SYSTEM&gt;
&lt;html&gt;
&lt;head&gt;
	&lt;meta http-equiv="content-type" content="text/html; charset=utf8"&gt;
	&lt;style type="text/css"&gt;
		/* CSS definice */
	&lt;/style&gt;
	&lt;title&gt;FATAL ERROR&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;&lt;div style="clear:both;font-size:0;"&gt;&lt;/div&gt;
	&lt;h1&gt;FATAL ERROR&lt;/h1&gt;
	%MSG%
	&lt;span id="fatal_error"&gt;
HTML;

$fatal_error_begin = strtr($err_begin_pattern, array(
	'%MSG%' =&gt; $GLOBALS['config']['error']['alt_message']
));

$err_end_pattern = &lt;&lt;&lt;HTML
	&lt;/span&gt;
	&lt;span id="request"&gt;&lt;/span&gt;
	&lt;script type="text/javascript"&gt;
	// &lt;![CDATA[
		var httpRequest = null;
		var sendRequest = %SEND%;
		var sendUrl = '%AJAX%';
		/**
		 * zde zavoláme Ajaxem externí skript
		 * znění chyby vytáhneme ze span#fatal_error		 
		 **/
	// ]]&gt;
	&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
HTML;

$fatal_error_end = strtr($err_end_pattern, array(
	'%SEND%' =&gt; $GLOBALS['config']['error']['email_send'],
	'%AJAX%' =&gt; $GLOBALS['config']['error']['ajax_url'],
));

ini_set('error_prepend_string', $fatal_error_begin);
ini_set('error_append_string', $fatal_error_end);
</code></pre>
<p>Jak je vidět ve výše uvedené ukázce, opět využijeme nastavení v globálních proměnných. Tam nastavíme alternativní chybovou hlášku, url, kam se bude posílat request a samotné zapnutí / vypnutí notifikačních e-mailů.</p>
<p>Nakonec to zase tak složité nebylo. Na co je ovšem opravdu nutné myslet, je, ošetření samotného error handleru. V daném skriptů totiž <strong>nesmí</strong> dojít k sebemenší chybičce. Pokud totiž nastane chyba ve funkci, která se o chyby má starat, dojde k zacyklení a nekonečnému volání sebe sama.</p>]]></content:encoded>
			<image>
    				<url>https://mike.treba.cz/img/2023/renewed/php.jpg</url>
			</image>
		</item>
		
	
</channel>
</rss>