PHP od začátku: dynamické skládání stránek funkcí include

Sun, 27 Jan 2008 14:10:25 +1100

V dnešním článku bych odbočil od středně pokročilých PHP skriptů a návodů, a vrátil se na chvíli k samotným začátkům většiny z nás, a to prvnímu použití funkce include. Existuje mnoho konstrukcí, kterými můžeme stránky poskládat z více souborů, a já vám popíši všechny jejich výhody a nevýhody. Pokusím se jít úplně od začátku, od prvního neandrtálského řešení až po šikovnou sekvenci podmínek s ošetřením všech potencionálních chyb a nedostatků.

Předpokládejme includování stránek podle proměnné $_GET['page'] a odkazy například ve tvaruindex.php?page=kontakt. Jako nejjednodušší řešení lze vytvořit posloupnost několika podmínek:

if ($_GET['page'] == 'kontakt') {
	include 'inc/kontakt.php';
} else if ($_GET['page'] == 'o-nas') {
	include 'inc/o-nas.php';
} else {
	include 'inc/uvod.php';
}

Jako první nedostatek bych zmínil absenci chybové stránky. Při zavolání adresy například index.php?page=tohle-neexistuje je načtena úvodní stránka. Lepší, než kdyby se zhroutil celý vesmír, ovšem my se chceme posunout dál. Proto můžeme vytvořit podmínku, která bude hned jako první, postará se o načtení úvodu při nulové hodnotě proměnné a chybovou stránku 404 zavolá až pod else. Nesprávně bývá uváděno porovnání if ($_GET['page'] == '') předcházející dalšímu testování proměnné $_GET['page'], a právě zde narážíme na další chybu: nedefinovaná proměnná totiž nemá hodnotu empty nýbrž null, tudíž musíme testovat na not null. Upravený zápis by mohl vypadat nějak takto:

if (!isset($_GET['page'])) { // špatně : if ($_GET['page'] == '')
	include 'inc/uvod.php';
} elseif ($_GET['page'] == 'kontakt') {
	include 'inc/kontakt.php';
} elseif ($_GET['page'] == 'o-nas') {
	include 'inc/o-nas.php';
} else {
	include 'inc/404.php';
}

Tohle řešení je ovšem stále čiré zlo. Jestliže máme například 20 podstránek, neustálé opakování if / elseif … je cesta do pekel. A zde přichází konstrukce switch, kterou implementujeme namísto zbytečných podmínek. Nezapomeneme ani na hodnotu null a chybová stránka 404 zůstane pod default.

switch($_GET['page']) {
	case null :
		include 'inc/uvod.php';
	break;
	case 'kontakt' :
		include 'inc/kontakt.php';
	break;
	case 'o-nas' :
		include 'inc/o-nas.php';
	break;
	default:
		include 'inc/404.php';
	break;
}

Mnohem lepší řešení než předchozí uvedené, ale to stále nestačí. Zde zmíním další nedostatek, který první a třetí zápis obsahuje: jestliže nastavíme úroveň výpisu chybových hlášek error_reporting(E_ALL), zmíněné konstrukce vypisují chybu typu notice. Navíc stejně jako sekvence podmínek musíme i ve switchi neustále přidávat další řádky, tři s každou novou podstránkou. Naštěstí ale přichází v pravou chvíli spásná funkce file_exists, díky které zkrouhneme desítky zbytečných řádků do jednoho if / else. Hodnotě proměnné přidáme koncovku *.php a zkontrolujeme, zdali daný soubor existuje. Pokud ano, includujeme.

if (file_exists('inc/' . $_GET['page'] . '.php')) {
	include 'inc/' . $_GET['page'] . '.php';
} else {
	include 'inc/uvod.php';
}

Jako základ stačí, ale my se přece nebudeme spokojení s žádným ořezaným řešením. Výše uvedená konstrukce opět nemyslí na chybová hlášení typu notice: undefined index, stejně tak na error 404 při nesmyslné hodnotě proměnné.

Nejdříve otestujeme proměnnou funkcí isset, v případě hodnoty false načteme úvodní stránku. Až uvnitř bude kontrola přes file_exists, načteme požadovaný soubor nebo 404ku. Dynamické načítání stránek může nyní vypadat nějak takto:

if (isset($_GET['page'])) {
	if (file_exists('inc/' . $_GET['page'] . '.php')) {
		include 'inc/' . $_GET['page'] . '.php';
	} else {
		include 'inc/404.php';
	}
} else {
	include 'inc/uvod.php';
}

Závěrečná ošetření

Problém může nastat ve chvíli, kdy uživatel odhadne naši adresářovou strukturu a vyzkouší, co se stane, když napíše od URL například "index.php?page=../index". V takovém případě nás totiž cesta zavede zpět na spouštěcí soubor, a ten bude includovat sám sebe až do zabití procesu. Nejsnazší řešení je použít "include_once" namísto "include", ovšem tady bychom dostali bílou stránkou. A my chceme zobrazit 404 šablonu. Navíc ošetříme notice, kdyby proměnná page náhodou byla array. Include_once necháme, protože tím člověk nic nezkazí.

if (isset($_GET['page']) && !is_array($_GET['page'])) {
	$page = str_replace(array('/', '\\'), '', $page);
} else {
	$page = null;
}

if (isset($page)) {
	if (file_exists('inc/' . $page . '.php')) {
		include_once 'inc/' . $page . '.php';
	} else {
		include_once 'inc/404.php';
	}
} else {
	include_once 'inc/uvod.php';
}

Unikátní titulek pro každou stránku

Bohužel, dynamická změna obsahu nestačí, je také vhodné tisknout každé podstránce unikátní titulek, keywords či description. Tuto funkcionalitu ale už nebudeme montovat do indexu, ale zanoříme ji k ostatním šablonám. Tedy výše uvedený blok kódu může být celý index.php.

Ošetření dalších includů uvnitř šablon lze realizovat několika způsoby. Podadresář. Jiný adresář v rootu. Jiná přípona. Já zvolil jinou příponu, protože šablony patří k sobě a měly by u sebe zůstat.

Nyní k samotnému titulku: z každé šablony "úvod", "o-nas" a "kontakt" vyjmeme hlavičku a patičku, vložíme ji jedinkrát do dalšího souboru a tam vyechujeme nastavenou proměnnou. Toť vše. Keywords a description můžeme vyřešit stejně.

uvod.php

<?php
	$title = 'Úvod';
	include_once '_header.phtml';
?>

Úvodní stránka

<?php include_once '_footer.phtml'; ?>

_header.phtml





<?php echo (!empty($title) ? $title : ''); ?> | Můj web




		Header


To by bylo snad vše, co jsem na srdci měl, a pro ty z vás, kteří by si chtěli stáhnout těch pár souborů, na kterých jsem toto testoval, nabízím ukázku ke stažení.

ZIP archív

Edit 26. 4. 2020: Článek byl zrevidován za účelem vhodnější struktury samotných šablon a lepšího ošetření vstupu.