Inicializace proměnných
Školení, která pořádám
Ve svých skriptech a funkcích zásadně inicializuji proměnné a nespoléhám se na to, že při prvním použití dostanou implicitní hodnotu (prázdný řetězec, nula, false, prázdné pole, apod.). Jednak to je účinná obrana proti podstrčení proměnné zvenku a jednak to zabraňuje těžko odhalitelným chybám, kdy je více souborů, které používají stejné proměnné, vloženo různě do sebe.
<?php
// špatný kód - kdokoliv může proměnnou $logged podstrčit zvenku nebo mohla být použita v jiném skriptu
if ($_POST["user"] == "root") {
$logged = true;
}
// správný kód
$logged = false;
if ($_POST["user"] == "root") {
$logged = true;
}
// úsporný kód
$logged = ($_POST["user"] == "root");
?>
Stejně důležité je i správně inicializovat pole, jak názorně ukazuje colder. Smutné je, že na nesprávně inicializované pole nás neupozorní ani E_NOTICE.
I přesto, že proměnné vždy inicializuji, tak nemám rád zobrazování chyb typu E_NOTICE. Důvod je ten, že při zapnutí těchto chyb nestačí inicializovat proměnnou typu pole, ale musí být inicializovány i jednotlivé prvky tohoto pole. Nejlépe to bude vidět na příkladu – dejme tomu, že chceme spočítat cenu výrobku v jednotlivých skupinách.
<?php
$vyrobky = array(
1 => array("skupina" => 1, "cena" => 200),
2 => array("skupina" => 2, "cena" => 300),
3 => array("skupina" => 1, "cena" => 100),
); // pole s výrobky, kde klíčem je ID výrobku a hodnotou pole obsahující skupinu, do které výrobek spadá, a jeho cenu
$ceny_skupiny = array(); // výsledné pole, kde klíčem bude ID skupiny a hodnotou výsledná cena
// kód bez E_NOTICE
foreach ($vyrobky as $val) {
$ceny_skupiny[$val["skupina"]] += $val["cena"];
}
// kód s E_NOTICE
foreach ($vyrobky as $val) {
if (!isset($ceny_skupiny[$val["skupina"]])) {
$ceny_skupiny[$val["skupina"]] = 0;
}
$ceny_skupiny[$val["skupina"]] += $val["cena"];
}
?>
Část if (!isset)
kód pouze znepřehledňuje, vnáší do něj balast a kódu jako takovému ničím nepřispěje (na rozdíl od obecné inicializace proměnných).
Kód je nepřehlednější i v případě vypisování hodnot, u kterých se nedá spolehnout na to, že budou nastaveny:
<?php
// bez E_NOTICE
echo '<input name="search" value="' . htmlspecialchars($_GET["search"]) . '" />';
// s E_NOTICE
echo '<input name="search" value="' . (isset($_GET["search"]) ? htmlspecialchars($_GET["search"]) : "") . '" />';
?>
A teď si ještě představte, jak by kód vypadal bez použití ternárního operátoru, který se někomu špatně čte.
Viz také Hledání neinicializovaných proměnných.
Přijďte si o tomto tématu popovídat na školení Bezpečnost PHP aplikací.
Diskuse
To já zase na E_NOTICE nedám dopustit. Na položky, které "možná jsou" mám funkci:
<?php
function &item(&$array, $index, $default) {
return (isset($array[$index])
? $array[$index]
: $default
);
}
?>
Také řeší to, že jako implicitní hodnotu nechci vždy null.
Nemáš pocit, že ten článek trošku popírá sám sebe? Tedy v prvním odstavi jsi "pro" inicializaci, a hned v druhém týkajícím se polí ji popíráš.
Nespoléhej se na to, že Ti nikdo nemůže podstrčit $ceny_skupiny[3] = 10;
Je to tam myslím popsané jasně. Inicializace polí je samozřejmě správná věc stejně jako u jakýchkoliv jiných proměnných, ale inicializace jeho jednotlivých prvků je podle mě samoúčelná. Je to koneckonců i v příkladě:
<?php
$ceny_skupiny = array();
?>
Díky tomu mám jistotu, že mi nikdo $ceny_skupiny[3] podstrčit nemůže.
bm:
A co operátor na potlačení chybových hlášení?
<?php
echo '<input name="search" value="' . htmlspecialchars(stripslashes(@$_GET["search"])) . '" />';
?>
Stefan:
Docela by me zajimalo, jak se daji podstrcit hodnoty vnitrnich promennych, tedy ne poli $_GET, $_POST apod.
V zásadě dvěma způsoby: Při zapnuté direktivě register_globals zcela zvenku, za všech okolností z vložených skriptů (tady spíše nedopatřením než cíleným útokem):
<?php
include "connect.inc.php";
if ($_POST["logged"] == "root") {
$auth = true;
}
?>
connect.inc.php:
<?php
$auth = "user";
$pass = "pass";
mysql_connect("localhost", $auth, $pass);
?>
Problém to může být hlavně u externích knihoven, které po sobě nechají nastavené nějaké pomocné proměnné.
Zdravim,
snazim se jeden web odprostit od varovani E_NOTICE, avsak asi spravne nechapu, jak inicializovat pole. Mam napr. tento kod: (zjednodusenne)
<?php
// Moznosti stylu v config souboru
$STYLY = array("classic" => "Klasický styl",
"mobil" => "Mobil / pda",
"print" => "Pro tisk");
// Pole zakladnich nastaveni:
$_GET_DEFAULT = array( "styl" => "classic");
// z kontroly poslanych promenych:
$styl_nastaven = "ne";
foreach($STYLY as $index => $hodnota){
// jestlize je poslano get, nastavime cookie:
if($_GET['styl'] == $index){
setcookie("styl", $_GET['styl']);
$styl_nastaven = "ano";
}
}
// jestlize je poslano cookie, nastavime cookie:
if(($styl_nastaven != "ano")){
foreach($STYLY as $index => $hodnota){
if($_COOKIE['styl'] == $index){
$_GET['styl'] = $index;
setcookie("styl", $_GET['styl']);
$styl_nastaven = "ano";
}
}
}
// jinak default styl:
if($styl_nastaven != "ano"){
$_GET['styl'] = $_GET_DEFAULT['styl'];
setcookie("styl", $_GET_DEFAULT['styl']);
}
?>
Stale vsak dostavam varovani e_notice. Jedna o neinicializovane prvky pole? Nebo jen stale nechapu jak je inicializovat?
Možná by stačilo si prohlédnout chybovou hlášku a zjistit, na kterém řádku k chybě dochází. Konkrétně to bude porovnávání $_GET['styl'] a $_COOKIE['styl']. To je potřeba nahradit něčím takovým: <?php (isset($_GET["styl"]) && $_GET['styl'] == $index) ?> nebo ještě lépe testem isset() obalit celý foreach cyklus.
Bravo! Obdivuji Tvou rychlost :)
Samozrejme ze to bylo hned na zacatku:
<?php
// jestlize je poslano get, nastavime cookie:
if($_GET['styl'] == $index){
}
// spravne tedy melo byt:
if(isset($_GET['styl']) && ($_GET['styl'] == $index)){
//dalsi kod...
}
?>
Dekuji, jsem zase o kousicek chytrejsi. Uz se Tesim, az si nekdy prijdu popovidat na nejake z Tvych jiste kvalitnich skoleni ;)
P.S. Nebylo by spatne, dat uzivateli pred odeslanim komentare nahled, jiste bych po sobe par veci jeste poopravil :)
Carlos:
Zdravím, zkouším a jsem z toho pořád nerozumný.
Původně jsem měl předání postu do proměné takto
<?php
$prijmeni = uprav_vstup($_POST["prijmeni"];
?>
<?php
//obecne upraveni vstupu pro cp1250 - snad dobře ???
function uprav_vstup($hodnota)
{
$hodnota = trim($hodnota);
$hodnota = str_replace("\"","",$hodnota);
$hodnota = str_replace("\'","",$hodnota);
return($hodnota);
}
?>
Hlásí pochopitelně Notice: Undefined index: prijmeni
Snažil jsem se udělat podle rady, ale neuspěšně.
$prijmeni = false;
if (uprav_vstup($_POST["prijmeni"])) {
$prijmeni = true;
}
Kde dělám chybu prosím? Děkuji
Michal:
Ne promin zapoměl jsem na tu fci, takto tedy:
<?php
function uprav_vstup($hodnota) {
$hodnota = trim($hodnota);
$hodnota = str_replace("\"","",$hodnota);
$hodnota = str_replace("\'","",$hodnota);
return($hodnota);
}
$prijmeni = isset($_POST["prijmeni"])?uprav_vstup($_POST["prijmeni"]):"";
?>
Michal:
Já myslím že problém jinde není. Tak jako tak musí být proměnná $prijmeni definovaná takže když test neprojde přes isset či array_key_exists musíš stejně řešit dál.
v6ak:
Doháje, nějak jsem si nevšiml isset. Ale isset nebo array_key_exists by to řešit mělo.
Já s tímto kódem problém nemám.
Michal:
Obě varianty jsou řešením, $_POST je také pole, takže proč nepoužít i array_key_exists, ale co ti přijde jednodušší:
<?php
$prijmeni = isset($_POST["prijmeni"])?uprav_vstup($_POST["prijmeni"]):"";
//nebo
$prijmeni = array_key_exists("prijmeni", $_POST)?uprav_vstup($_POST["prijmeni"]):"";
?>
Pro univerzálnost bych to ještě schoval do funkce, pokud se testuje více proměnných, příp. pole.
Hever:
Dlouho jsem s tímto problémem bojoval, nesouhlasím s iniciováním prvků pole, proto jsem E_NOTICE nepoužíval.
Nejčastěji mě zajímá jestli ten který prvek pole obsahuje danou hodnotu a jen kvůli pravidlům E_NOTICE zapisovat např.
<?php
if(isset($_GET['seradit']) and $_GET['seradit']!='')
// místo
if($_GET['seradit'])
?>
mi přišlo samoúčelné a ničící přehlednost kódu.
Ale zároveň mě časem začaly zajímat ostatní E_NOTICE chyby, tak jsem to nakonec vyřešil ve metodě, která chyby odchytává a zpracovává (k odchytávání chyb využívám Nette Debug)
<?php
public static function _errorHandler($severity, $message, $file, $line, $context)
{
if(strpos($message,'Undefined index') === 0) return ;
if(strpos($message,'Undefined variable: return') === 0) return ;
...
}
?>
E_NOTICE si tedy zaznamenávám, ale chyby spojené s neiniciováním prvků pole zahazuji.
Stejně tak zahazuji chyby s neiciovanou proměnou $return, kterou používám v 80% metod - podstrčení hodnoty je nemožné a i kdyby se tak snad stalo, neohrozilo by to bezpečnost. Do $return vždy skládá výstupní string.
Michal Prynych:
Já celkem úspěšně používám konstrukci empty(), která řeší jak inicializovanost, tak i prázdný řetězec, nulu a false
<?php
if(!empty($_GET['seradit']))
?>
Hever:
Dal jsem příklad s prázdnou hodnotou, ale jindy chci porovnávat s nějakou danou hodnotou.
<?php
if($_GET['seradit']=='cena')
?>
Nebo vypsat prvek v poli a když neexistuje, tak nevypisovat nic.
<?php
echo isset($pole['prvni']['text']) ? $pole['prvni']['text'] : ''); //php 5.2 korektní
//versus
echo $pole['prvni']['text'];
?>
Michal Prynych:
Pak to samozřejmě použít nelze, spíš jsem chtěl říct, že je to šikovná konstrukce pro případy testování na true/false včetně kontroly na inicializovanost.
Hever:
Výborně, PHP7 řeší problém s Undefined index pomocí Null Coalesce Operator
https://wiki.php.net/rfc/isset_ternary
Pár let jsem přečkal s "textem chybové zprávy", která se zatím nijak neměnila :) a definitivní řešení za chvíli vyjde v nové verzi.
Diskuse je zrušena z důvodu spamu.