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.

Jakub Vrána, Dobře míněné rady, 8.4.2005, diskuse: 34 (nové: 0)

Diskuse

ikona Milan Kryl:

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ě.

ikona Jakub Vrána OpenID:

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.

ikona spaze:

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.

ikona Jakub Vrána OpenID:

Ono to je úplně jedno a už jsme to probírali http://php.vrana.cz/obrana-proti-sql-injection.php#d-75

Llaik:

Krome hroziciho injection utoku (viz prispevek Milana) bych nedoporucil uvadet indexy pole mimo uvozovky (tj. $_POST['jmeno'] misto $_POST[jmeno]). Viz: http://cz.php.net/manual/en/language.types.….array.donts

ikona Jakub Vrána OpenID:

Pleteš si dvě věci:
<?php
$row
[id]; // špatně, nikde nepoužívám
"$row[id]"; // správně, používám kdekoliv
?>

http://www.php.net/manual/en/language.types.….string.parsing

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

ikona Jakub Vrána OpenID:

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. :-)

ikona Jakub Vrána OpenID:

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ě.

ikona mirek:

<?php
echo "Stránka byla zobrazena {$PocetZobrazeni} krát.";
?>

ikona 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í :-)

ikona Jakub Vrána OpenID:

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.

ikona 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.

ikona 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ř).

ikona Jakub Vrána OpenID:

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.

ikona Jakub Vrána OpenID:

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ď.

ikona llook:

Původně jsem chtěl napsat jen komentář, o tom, že to je vhodné jen pro zápis řetězců do DB a co s tím.
Nakonec se mi to natolik rozrostlo, že jsem to publikoval u sebe, snad nebude vadit odkaz: http://llook.wz.cz/weblog/spot/insert-into-v-php.php

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 ?

ikona Jakub Vrána OpenID:

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.

ikona Jakub Vrána OpenID:

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.....

ikona 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

ikona Jakub Vrána OpenID:

<?php
$set
["heslo_md5"] = "MD5('$_POST[heslo]')"; // MySQL funkce
$set["heslo_md5"] = "'" . md5(stripslashes($_POST["heslo"])) . "'"; // PHP funkce
?>

ikona 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.

ikona Jakub Vrána OpenID:

http://php.vrana.cz/spolecny-formular-pro-…-zaznamu.php

ikona 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'; ?>

ikona Jakub Vrána OpenID:

Až na to, že PHP 6 možná nikdy vůbec nebude (přestože se tak nabubřele jmenuje několik knih). http://schlueters.de/blog/archives/128-Future-of-PHP-6.html

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.