Jsem zastáncem toho, že člověk se mnohem rychleji učí na hotových částech, kde může posuzovat kontext a na kterých je mu vysvětleno co a jak. Pokusím se o tuto praktiku při vysvětlení několika více či méně důležitých věcech kolem programování v PHP.
Naším pokusným králíkem bude programátor, který dostal za úkol napsat jednoduchou online hru. Hra spočívá v tom, že uživatel si vybírá jedno z deseti polí. Oněch deset polí obsahuje náhodně rozmístěná skrytá nebezpečí. Vybrání takového pole znamená pro hráče konec hry. Pokud se mu podaří vybrat pole bezpečné, připočítá se mu do score 1 bod a pokračuje dalším kolem. Jak vidíte, hra není složitá a a ni její implementace nebude.
Programátor dodal tento skript. O kterém nám řekl, že to je pouze první ukázka a určitě se nejedná o finální kód. Složku se soubory si můžete stáhnout Zde
<?php
session_start();
// zpracovani vstupnich parametru
$action = $_POST["action"];
// *** sekce jednotlivych akci ***
function OnDefault(){
global $HTTP_SESSION_VARS;
echo "<html>";
echo "<head>";
echo "<title>Mine Field Runner 1.0</title>";
echo "<meta name=\"Copyright\" content=\"genesis, genesis@cardici.com\" />";
echo "</head>";
echo "<body>";
// zahlavi
echo "<H1>Mine Field Runner</H1>";
// score
$score = 0+$HTTP_SESSION_VARS["score"];
echo "<H2>Score $score bodu</H2>";
// herni pole
echo "<form action=\"?\" method=\"POST\">";
// vykresli 10 buttonu
for ( $f = 1; $f < 11; $f++ ){
echo "<input type=\"submit\" name=\"fieldcode\" value=\"$f\" style=\"width:20px;\"/>";
}
echo "<input type=\"hidden\" name=\"action\" value=\"STEP\"/>";
echo "</form>";
echo "</body>";
echo "</html>";
}
function OnStep( $code ){
global $HTTP_SESSION_VARS;
$score = 0+$HTTP_SESSION_VARS["score"];
$code = 0+$code;
// nasazime miny
$minecount = round($score / 5);
srand( time() );
$row = array( $false, $false, $false, $false, $false, $false, $false, $false, $false, $false );
if ( $minecount < 10 ) {
for( $f = 0; $f < $minecount; $f++ )
{
$index = rand(1,10);
$row[$index] = true;
}
} else {
for( $f = 1; $f <= 10; $f++ )
{
$row[$f] = true;
}
}
if ( $row[$code] ){
// mina
global $HTTP_SESSION_VARS;
echo "<html>";
echo "<head>";
echo "<title>Mine Field Runner 1.0</title>";
echo "<meta name=\"Copyright\" content=\"genesis, genesis@cardici.com\" />";
echo "</head>";
echo "<body>";
// zahlavi
echo "<H1>Mine Field Runner</H1>";
// score
$score = 0+$HTTP_SESSION_VARS["score"];
echo "<H2>Score $score bodu</H2>";
// herni pole nahradime hlaskou, ze leti vzduchem
echo "<p>Lituji, ale právě jste šlápl na minu. Snad příště.</p>";
echo "<a href=\"?\">Hrát znovu</a>";
echo "</body>";
echo "</html>";
$HTTP_SESSION_VARS["score"] = 0;
}
else
{
// volne pole
$HTTP_SESSION_VARS["score"] = $score + 1;
OnDefault();
}
}
// *** /sekce jednotlivych akci ***
// *** centralni zpracovani ***
switch( $action ){
case "STEP" : {
OnStep( $_POST["fieldcode"] );
break;
}
default :{
OnDefault();
break;
}
}
// *** /centralni zpracovani ***
?>
session_start();
// zpracovani vstupnich parametru
$action = $_POST["action"];
// *** sekce jednotlivych akci ***
function OnDefault(){
global $HTTP_SESSION_VARS;
echo "<html>";
echo "<head>";
echo "<title>Mine Field Runner 1.0</title>";
echo "<meta name=\"Copyright\" content=\"genesis, genesis@cardici.com\" />";
echo "</head>";
echo "<body>";
// zahlavi
echo "<H1>Mine Field Runner</H1>";
// score
$score = 0+$HTTP_SESSION_VARS["score"];
echo "<H2>Score $score bodu</H2>";
// herni pole
echo "<form action=\"?\" method=\"POST\">";
// vykresli 10 buttonu
for ( $f = 1; $f < 11; $f++ ){
echo "<input type=\"submit\" name=\"fieldcode\" value=\"$f\" style=\"width:20px;\"/>";
}
echo "<input type=\"hidden\" name=\"action\" value=\"STEP\"/>";
echo "</form>";
echo "</body>";
echo "</html>";
}
function OnStep( $code ){
global $HTTP_SESSION_VARS;
$score = 0+$HTTP_SESSION_VARS["score"];
$code = 0+$code;
// nasazime miny
$minecount = round($score / 5);
srand( time() );
$row = array( $false, $false, $false, $false, $false, $false, $false, $false, $false, $false );
if ( $minecount < 10 ) {
for( $f = 0; $f < $minecount; $f++ )
{
$index = rand(1,10);
$row[$index] = true;
}
} else {
for( $f = 1; $f <= 10; $f++ )
{
$row[$f] = true;
}
}
if ( $row[$code] ){
// mina
global $HTTP_SESSION_VARS;
echo "<html>";
echo "<head>";
echo "<title>Mine Field Runner 1.0</title>";
echo "<meta name=\"Copyright\" content=\"genesis, genesis@cardici.com\" />";
echo "</head>";
echo "<body>";
// zahlavi
echo "<H1>Mine Field Runner</H1>";
// score
$score = 0+$HTTP_SESSION_VARS["score"];
echo "<H2>Score $score bodu</H2>";
// herni pole nahradime hlaskou, ze leti vzduchem
echo "<p>Lituji, ale právě jste šlápl na minu. Snad příště.</p>";
echo "<a href=\"?\">Hrát znovu</a>";
echo "</body>";
echo "</html>";
$HTTP_SESSION_VARS["score"] = 0;
}
else
{
// volne pole
$HTTP_SESSION_VARS["score"] = $score + 1;
OnDefault();
}
}
// *** /sekce jednotlivych akci ***
// *** centralni zpracovani ***
switch( $action ){
case "STEP" : {
OnStep( $_POST["fieldcode"] );
break;
}
default :{
OnDefault();
break;
}
}
// *** /centralni zpracovani ***
?>
Pustíme se tedy do analýzy hotového kódu.
Na řádce #2 vidíme, že programátor startuje session. Tento mechanismus nám umožní uchovávat na serveru uživatelská data s rozlišením pro jednotlivé uživatele.
Řádka #5 obsahuje načtení proměnné ze vstupu od uživatele. Podle názvu proměnné můžeme usuzovat, že se jedná o akci, kterou hráč udělal. Zde je zajímavá druhá část řádky, kde vidíme, že hodnota je čtena ze serverové proměnné obsahující pouze data odeslaná pomocí POST metody formuláře. Tato serverová proměnná je plněna automaticky.
Řádky #11-#37 pak skrývají funkci, jejímž úkolem je vypsání obrazovky s hracím polem. V začátku vidíme, že je do funkce registrována globální serverová proměnná obsahující data uložená do session. Registrace také proměnné pomocí klíčového slova global je důležitá pokud je PHP nakonfigurováno tak, aby nešířilo globální proměnné dovnitř funkcí. Jedná se o nastavení, které sice lehce ztrpčí život, otevírá však některé možnosti a zvyšuje bezpečnost aplikací. Dále vidíme, že je ve funkci čteno score, které je sčítáno s číslem 0, tedy je zde vidět snaha o získání čísla. Tento způsob kontroly vstupních dat ale není zrovna ideální, místo toho by měla být použita funkce pro zjištění typu hodnoty, zde bychom použili funkci is_int nebo is_integer, a podle výsledku bychom se zachovali.
Řádky #40-#92 pak obsahují druhou funkci provádějící výběr pole a vyhodnocení správnosti tohoto výběru. Na začátku opět vidíme zaregistrování globálních proměnných a načtení hodnot do proměnných. Na řádce #49 je pak vytvořeno pole hodnot představující sadu políčka, následně je v tomto poli přepnuto několik hodnot, výsledkem je, že hodnota false představuje prázdné pole a hodnota true obsahuje pole s nebezpečím. Umístění hodnot je náhodné a tato náhodnost je dána funkcí rand která nám z daného intervalu včetně hraničních hodnot náhodně vybere hodnotu. Aby však tato hodnota byla maximálně nepředvídatelná, je ještě před vytvořením pole provedena inicializace generátoru náhodných čísel a to pomocí funkce srand které je předhozen aktuální čas. Pokud bychom tuto funkci nepoužili, s vysokou pravděpodobností by se generovala neustále stejná řada hodnot. Použití této funkce přednastaví generátor podle aktuálních hodnot a výsledkem je odlišná řada čísel při každém volání. Dále ve funkci vidíme už jen porovnání, zda měl hráč štěstí nebo ne. Pokud ne, je mu vypsána obrazovka a vynulováno score, pokud ano, je mu připočítán bod do score a nová hodnota score je uložena do session pro příští použití.
Mezi řádky #99 a #108 pak vidíme stavy stránky. Podle akce, kterou uživatel zvolil a kterou jsme přečetli na začátku skriptu se rozhodujeme, zda uživatel provedl výběr pole nebo zda mu pouze vypíšeme informační obrazovku. Je zde použita konstrukce pro několikanásobné větvení tedy switch, který obsahuje jednotlivé možné hodnoty a reakce na ně. Zde vidíme reakci na hodnotu STEP a v poslední větvi vidíme obsloužení všech ostatních hodnot.
Jak tedy náš programátor obstál?
Programátor sice splnil zadání a s odhlédnutím od grafické stránky však máme několik výhrad.
1.) Programátor míchal přístupy ke globálním přednastaveným serverovým proměnným a to k $_POST a $HTTP_SESSION_VARS . Jedná se o téměř stejné přístupy, ovšem je zde důležité nastavení konkrétního PHP na konkrétním serveru. Obvykle lze získat nastavení zavoláním jednoduchého skriptu obsahujícího volání jediné funkce a to phpinfo() -ukázka výstupu Zde. Tato funkce vygeneruje html stránku obsahující všechny dostupné informace o nastavení PHP a všech modulů. V jedné ze sekcí je vidět i jaké serverové proměnné jsou plněny a co se v nich právě skrývá. O tyto informace by se měl programátor opřít a nemíchat přístup k dvěma sadám proměnných a to $_<zdroj> a $HTTP_<zdroj>_VARS .
2.) Další výhrada je spíše moje osobní, pokud tedy je funkčnost implementována v jediném skriptu, je představené řešení sice krásné, ale bylo by vhodnější logiku, která je zde uvedena na konci skriptu, uvést před definicí jednotlivých zpracovávajících funkcí. Pomohlo by to ke snadnějšímu čtení skriptu.
3.) Další výhradou je výhrada k chování vůči hodnotám od uživatele. Obecně platí pravidlo „Nikomu nevěř“, které tedy znamená kontrolovat správně všechny hodnoty vstupující do skriptu z jiných zdrojů. Ve skriptu je sice naznačen pokus o správné získání hodnoty, ale jak jsem se již zmínil, bylo by lepší použít skupinu funkcí is_<typ> , které jsou přímo stavěny na to, aby umožnily kontrolu hodnot v proměnných na očekávaný datový typ. Nikde ve skriptu pak není kontrolováno podtečení nebo přetečení hodnot apod.
4.) Výpis uživatelského HTML by mohl být také flexibilnější. Při této architektuře by jakákoliv změna designu HTML stránek znamenala úporné změny v kódu a hodně práce. Pro vyhnutí se takovým problémům by bylo vhodné opakovaně vypisované části HTML umístit do funkcí a následně volat pouze tyto funkce. Typickými adepty jsou například záhlaví a zápatí stránky.
V příští části se pokusíme některé chyby odstranit, zároveň budeme hráči pod řádku s tlačítky vypisovat řádku se zobrazením, kde v předchozím kole byla nebezpečí a kde ne.



