Psaní INSERT INTO
Školení, která pořádám
Spousta programátorů si snaží uchránit klávesnici tím, že místo INSERT INTO tabulka (sloupce) VALUES (hodnoty)
píše pouze INSERT INTO tabulka VALUES (hodnoty)
. Kromě toho, že pro neznalého člověka je tento zápis mnohem hůře čitelný (netuší, jaké sloupce v tabulce jsou), přidělává také problémy v případě, kdy se rozhodneme v tabulce např. změnit pořadí sloupců nebo do ní přidat sloupec další (byť třeba timestamp nebo jiný sloupec s defaultní hodnotou). Já osobně pro netriviální dotazy používám tento zápis:
<?php
$set = array();
$set["jmeno"] = "'" . mysql_real_escape_string($_POST["jmeno"]) . "'";
$set["prijmeni"] = "'" . mysql_real_escape_string($_POST["prijmeni"]) . "'";
$set["telefon"] = "'" . mysql_real_escape_string($_POST["telefon"]) . "'";
// a další spolu s případným ošetřením hodnot
mysql_query("INSERT INTO uzivatele (" . implode(", ", array_keys($set)) . ") VALUES (" . implode(", ", $set) . ")");
?>
Je to sice o něco víc psaní, ale přehlednosti a rozšiřitelnosti se podle mě nedá nic vytknout.
MySQL umožňuje psát také "INSERT INTO uzivatele SET jmeno = '$_POST[jmeno]', …"
, ale vzhledem k tomu, že to není standardní způsob zápisu a nepřináší žádné podstatné výhody, tak ho nepoužívám.
Ze stejné kategorie chyb, ale snad ještě horší, je používání SELECT *
spolu s mysql_fetch_row nebo spolu s číselnými indexy při mysql_fetch_array.
Přijďte si o tomto tématu popovídat na školení Návrh a používání MySQL databáze.
Diskuse
Přímého psaní POST proměnných do dotazu bych se vyvaroval. Dokonce bych v článku varoval, protože méně zkušený programátor lehce zapomene na ošetření chyb a SQL injection díra je na světě.
Všechen kód na tomto blogu předpokládá zapnuté magic_quotes_gpc a je tedy bezpečný. Dám to asi někam do patičky.
Ošetřovat data v ukázkách např. funkcí addslashes() je také špatně, protože když je magic_quotes_gpc zapnuté (což je default), data budou escapovaná dvakrát. A používat funkci, která data ošetří na základě nastavení této direktivy, by kód zbytečně znepřehlednilo.
ono taky spis nez pouzivat addslashes() by se melo napr mysql_real_escape_string(), nicmene na situaci to nic nemeni :) Situaci muze zmenit az "filter" extension, ktera je snad planovana do 5.1.
Llaik:
"$blabl" spatne, protoze to zbytecne zpomaluje parser, lepsi? :)
Llaik:
Tedy tim jsem chtel rici: pokud se tu nekolik zkusenejsich programatoru vyjadri ohledne nejasnosti nekterych veci, neni mozna lepsi byt opravdu popisnejsi, aby blog splnil svuj ucel, tj. byti napomocny zacinajicim?
Napis v paticce o magic_quotes je fajn, ale je dostatecny? Je zapis promennych do stringu ".." je vhodny? Apod
Jakub Vrána :
V příspěvcích chci jít rychle k jádru věci, proto se nemůžu u každého druhého zabývat nastavením magic_quotes_gpc, případně filozofickými debatami na toto téma.
Psaní proměnných do řetězců je zcela v pořádku a ničemu to nevadí. Přijde mi přehlednější, proto ho používám a používat budu.
Gaddy:
<?php
"$row[id]"
?>
Tak tento způsob zápisu bych asi nepoužil a taky nedoporučoval, obzvlášť na stránkách, kde se začátečníci učí.
Představme si, že nějaký začátečník bude chtít napsat počet, kolikrát byla stránka zobrazena v následujícím formátu.
Stránka byla zobrazena 3krát.
Otevře textový editor a natříská tam:
<?php
echo "Stránka byla zobrazena $PocetZobrazeni krát.";
?>
Koukne jak to vypadá a uvidí za číslem mezeru. Tak ji smaže:
<?php
echo "Stránka byla zobrazena $PocetZobrazenikrát.";
?>
Co uvidí tentokrát? No asi kulové, že? Jak dlouho takovému nadšenci bude trvat, než přijde na něco takového:
<?php
echo "Stránka byla zobrazena " . $PocetZobrazeni . "krát.";
?>
Žádná věc není blbuvzdorná. Třeba si uvědomit, pro koho články jsou psány a nenutit jim své návyky. :-)
Jakub Vrána :
Pokud se ti zdá používání proměnných uvnitř řetězců špatně pochopitelné, tak sis asi nevybral správný programovací jazyk, protože se to v PHP používá zcela běžně.
To, že proměnná končí prvním znakem, který nemůže tvořit její jméno, je zcela intuitivní. Komu to intuitivní nepřijde, neměl by programovat vůbec.
Navíc "$row[pocet]krát" funguje výborně.
mirek:
<?php
echo "Stránka byla zobrazena {$PocetZobrazeni} krát.";
?>
dgx:
zapnuté magic_quotes_gpc už bylo na tomto blogu zmíněno hodněkrát, já nevím, asi bych to přehodnostil. Je jen otázka času, kdy bude tato direktiva vypnuta (protože je vážně velmi jednoúčelová a ne každý chce nezpracovaná data hned šupnou do databáze). A jako pokrokový PHP blog bys to mohl reflektovat ;-) Výsledek bude ten, že pod každým článkem nenajdeš třicet OT komentářů týkajících se slashování :-)
Jakub Vrána :
Mě taky vadí, že v PHP taková direktiva vůbec je, ale když už tam je, tak ji používám (jak už jsem psal), protože mi přináší možnost psát kratší a přehlednější kód, což se v příkladech hodí. Existují myslím tři skupiny lidí:
1. Netuší nic o SQL injection.
2. Znají SQL injection a neznají magic_quotes_gpc.
3. Znají oboje.
Alespoň jedna z těchto tří skupin bude vždy nutně protestovat.
llook:
Asi jsem exot, ale já dřív věděl o SQL injection než o magic_quotes_gpc.
Psal jsem si kdysi jeden skriptík v PHP, poctivě jsem dával addslashes při sestavování všech SQL dotazů a všechno mi perfektně fungovalo.
Pak jsem to nahrál na Webzdarma a nestačil se divit.
spaze:
"MySQL umožňuje psát také INSERT INTO tabulka SET jmeno = '$_POST[jmeno]', ..., [...] nepřináší žádné podstatné výhody"
Pri vytisteni toho dotazu je to pak mnohem prehlednejsi, ale standardni to neni, coz je mozna skoda :)
Pachollini:
Mně připadá rozumné, udělat si vlastní funkci na escapování řetězců pro SQL a všude je cpát. Pokud je zapnutá magika, vrátí, co jí bylo předhozeno, pokud ne, udělá mysql_real_escape_string a pokud od ní vu budoucnu budu chtít víc, připíšu to tam. Nebo ještě lépe používat pro SQL dotazy vlastní třídu a v ní implementovat všechny tyhle věci. Dělám to tak všude od té doby, co jsem převáděl jednu aplikace z MySQL na MSSQL ;-)
Myslím, že je vůbec dobré, obalit skoro všecko v PHP vlastní funkcí nebo třídou, zejména věci, co se často mění (XML/XSLT např).
Jakub Vrána :
Já si to nemyslím, protože kód je pak pro neznalého podstatně hůř čitelný. Nad touto nevýhodou mohou u některých projektů převážit výhody, ale do příkladů pro široké publikum se to nehodí ani v nejmenším.
Funkce pro ošetření dat v závislosti na magic_quotes_gpc už byla zmíněna: http://php.vrana.cz/obrana-proti-sql-injection.php
RejpalCZ:
Já to používám přesně tak, jak píše kolega - už jsem to tu jednou psal v komentářích k proměnným zvenku - zvyknout si přistupovat ke všem proměnným přes vlastní funkci. Je to přehlednější, rychlejší na psaní, bezpečnější, snadněji upravitelné a začátečník se naučí funkce používat tak, jak by měl - tedy co nejčastěji a s důrazem na znovupoužitelnost a snadnou změnitelnost kódu.
Vážně nechápu, proč se tomu pořád tak bránite.
Jakub Vrána :
Já se tomu nebráním a připouštím, že u některých projektů se to může vyplatit. U jiných ale zase ne a je to rozhodně i případ příkladů v mých článečcích.
Přehlednost, rychlost psaní, bezpečnost a snadná upravitelnost je do značné míry subjektivní, ale pokud mám možnost ovlivnit konfiguraci PHP (což mám), máloco v přehlednosti a rychlosti psaní překoná obyčejné $_POST["a"]. Bezpečnost je stejná a rozšiřitelnost u takovéto triviality není potřeba.
Honza Odvárko:
Taky jsem k tomu dlouho používal vlastní funkci. časem ale přestal, protože zabalovat do ní každé $_GET, $_POST atd. mě časem začalo nudit. A pak kód, ach ten kód! Změť čehosi nepřehledného.
Čili další variantou je - což teď používám - zuby nehty uvést magic_quotes_gpc do stavu který je třeba. Jednak to zkusit .htaccess souborem (kde PHP_INI_PERDIR změnit lze), a pak ještě includuju skript env.inc.php který přednastavuje prostředí, čili mj. zkusí žádaný stav magic_quotes_gpc emulovat - pro případ, že .htaccess selhal.
Ono je to takové... jakože každému jeho šálek čaje. Kdo data z GPC používá předně v dotazech, ten magic_quotes_gpc uvítá, protože má kód čistější. Kdo s nimi dělá jiná kouzla, ten *IMHO* udělá pro přehlednost lépe když zařídí MQ vypnuté.
á propos doufám že Jakubovi nevadí, že píšu pod rok staré články, objevil jsem je až teď.
Tomáš:
Já používám toto, je to výňatek s trochu větší třídy, ale funkce je myslím zřejmá:
<?php
/**
* Funkce vloží zadané pole do databáze tak, že ve dvojici "klíč"=>"hodnota"
* akceptuje klíč jako název sloupce v databázi.
*
* Vkládán není sloupec 'id', u něhož se předpokládá, že je to primární klíč a
* že je autoincrement.
*
* Funkce si rovněž hlídá existenci sloupců v databázi a vkládá jen existují
*
* Pokud některý sloupec chybí a v databázi má povolenu null hodnotu, bude
* chybějící sloupec na tuto hodnotu nastaven.
*
* @param string $table_name název tabulky do které se má vkládat
* @param array $insert_array vkládané pole
* @param string $query_name
* @param boolean $force_inser_id explicitně přinutí vkládat i sloupec id
*
* @return vrátí výsledek jenž vrátí metoda {@link query_execute()} po zavolání INSERT
*/
function autoInsertRow($table_name,$insert_array,$query_name=null,$force_inser_id = false) {
//kontrola vstupních parametrů
assert(is_array($insert_array));
assert(!empty($table_name));
// získání seznamu sloupců v tabulce
$cols = $this->getCols($table_name);
foreach($cols["names"] as $cnum => $col) {
if ($col!="id" || $force_inser_id) {
if(isSet($insert_array[$col])) {
$ncols[] = "`$col`";
$values[] = "'".$this->escapeString($insert_array[$col])."'";
} elseif ($cols["info"][$cnum]["Null"] == "YES") {
$ncols[] = "`$col`";
$values[] = "null";
}
}
}
// sestavení dotazu
$ncols = join(",",$ncols); $values = join(",",$values);
$query = "INSERT INTO `$table_name` ($ncols) VALUES ($values)";
return $this->query_execute($query,"class.sql -> autoInsertRow():" . $query_name);
}
?>
Jan Kotva:
Dobrý den. Nemam moc zkuzsenosti proto se ptam :
Proc je používání SELECT * spolu s mysql_fetch_row nebo spolu s číselnými indexy při mysql_fetch_array
chybou ?
Jakub Vrána :
Protože když se do tabulky přidá sloupec jinam než na konec, přestane stávající kód fungovat.
Zdenek:
Má někdo čas zodpovědět otázku? : Jak můžu zjistit (v php stránce) že existuje v tabulce řádek s id = 100? Předpokládám, že v tabulce je právě 99 řádků a php kód chce právě vypsat řádek 100 do tabulky, ale vypíše chybu, protože mysql_result hledá na neexistujícím řádku. Děkuji.
Jakub Vrána :
Přečti si text pod nadpisem Vložit příspěvek, tam najdeš odpověď.
Petr:
Existuje prece i PHP fce: HTMLSpecialChars, i kdyz ja ji pouzivam hlavne pri vkladani dat do databaze.....
Ivo:
Mohl bys ukázat jak některá z těch dat z kolekce POST upravíš (např. md5 pro heslo). Nějak se v těch uvozovkách a apostrofech ztrácím. Děkuji
Jakub Vrána :
<?php
$set["heslo_md5"] = "MD5('$_POST[heslo]')"; // MySQL funkce
$set["heslo_md5"] = "'" . md5(stripslashes($_POST["heslo"])) . "'"; // PHP funkce
?>
Ivo:
Díky moc, už to chápu. A používáš i nějakou takovou fintu na update? Tohle mi značně ulehčilo práci, děkuji.
zacatecnik:
Hošíci nechci Vám do toho krandat, do Vaší debaty :) Ale mám za to, že metod jak dojít k výsledku v php je asi miliarda :) Každý používá to, čemu rozumí a to, o čem si myslí, že to je to nejelpší. PHP se vyvíjí a návyky, které máte, budou čas od času potřeba zmodernizovat. V PHP 6 bude jistě vše už trochu zase jinak a zas bude o čem mluvit :) <?php print 'haugh'; ?>
Diskuse je zrušena z důvodu spamu.