Neinicializované proměnné

Školení, která pořádám

Většina pokročilejších PHP programátorů si uvědomuje rozdíl mezi neinicializovanou a prázdnou proměnnou. Pokud je zapnutá úroveň chyb E_NOTICE, tak je znalost tohoto rozdílu dokonce nezbytná, protože na každou práci s neinicializovanou proměnnou je programátor upozorněn.

Málokdo ale ví, že rozdíl je i mezi nenastavenou proměnnou a proměnnou obsahující hodnotu null. Tento rozdíl nám neprozradí ani funkce isset, ani třeba var_dump:

<?php
$empty = "";
$null = null;
var_dump($empty); // string(0) ""
var_dump($null); // NULL
var_dump($unset); // NULL
?>

Rozdíl spočívá v tom, že proměnná obsahující hodnotu null zůstává v tabulce symbolů, kdyžto nenastavená nebo odnastavená proměnná v této tabulce vůbec není. Dobře to je vidět třeba na práci s poli:

<?php
$a = array('unset' => true, 'null' => true);
unset($a['unset']); // prvek z pole zcela zmizí
var_dump($a); // array(1) { ["null"] => bool(true) }
$a['null'] = null; // prvek v poli zůstane, ale bude obsahovat hodnotu null
var_dump($a); // array(1) { ["null"] => NULL }
?>

Existence proměnné se dá zjistit kódem array_key_exists("a", get_defined_vars()).

Přiřazení hodnoty null také na rozdíl od funkce unset nezruší referenci:

<?php
$a = 3;
$b = &$a;
$b = null;
var_dump($a); // NULL
?>

Podtrženo, sečteno: Přiřazování hodnoty null do proměnné je lepší se zcela vyhnout – ke zrušení proměnné slouží funkce unset a při inicializaci je lepší proměnné přiřadit rovnou prázdnou hodnotu toho typu, kterého bude.

Jakub Vrána, Výuka, 14.11.2005, diskuse: 15 (nové: 0)

Diskuse

ikona dgx:

> Přiřazování hodnoty null do proměnné je lepší se zcela vyhnout

Pokud to někdo používá jako náhradu za zrušení proměnné, tak jednoznačně, ale jinak je přiřazení NULL zcela legitimní operace. Naopak v případě polí (proměnných) objektů je rušení docela nevhodné, lepší je nastavit na NULL.

migon:

nikdy sem nevidel aby nekdo takhle (uchylne) promenou rusil...

ze by stesi :-)

Miloslav Ponkrác:

Náhodou null je velice praktická věc. Velmi praktické na ní je i to, že se jedná o zvláštní typ, nikoli jen o hodnotu nějakého běžného typu.

Na null mi nesahejte! Kolikrát jsem si přál, aby třeba mysql funkce z PHP vracely null hodnotu, je-li ve sloupci v databázi null. Ale bohužel PHP to dělá tak nelogicky, že slupce, které mají v databázi null jsou ve výsledku v PHP nedefinované proměnné. Co nadělám, no.

Takový Oracle se v PHP chová daleko účelněji. Pro null hodnoty v databázi vrací null hodnotu proměnné.

Kromě toho je na null taky šikovné, že je to hodnota, kterou vrací funkce, když použiju return bez hodnoty. Sice to není čísté řešení, ale je to z hlediska PHP konzistentní řešení. Takže třeba následující kód uloží do proměnné null hodnotu:
<?php
function pokus()
{
  return;
}

$promenna = pokus();
?>
Hodnota null rozhodně není určena k rušení proměnných, to o ní nikdo netvrdil. Je to prostě hodnota jako každá jiná.

ikona dgx:

V dokumentaci k "mysql_fetch_array" stojí:

Note:
This function sets NULL fields to PHP NULL value.

Skutečně vám to proměnnou nevrací?

ikona Jakub Vrána OpenID:

Mě PHP v tomto případě také vrací NULL.

burro.b:

Hehe, funkce jejímž výstupem bude vždycky NULL!! Asi se někde zavřu a třeba za 10 let přijdu na něco, na co tuhle funkci využiju a udělám NULL do světa....

Hologos:

Na to není potřeba se nikam na 10 let zavírat.
Může to použít třeba takhle

<?php
function getValue()
{
    if(!is_integer($this->var))
        return NULL;

    return $this->var;
}
?>

Pokud nechce používat vyjímky a funkce mu vrací integer (tedy pro indikaci chyby nemůže použit ani 0 ani -1), NULL zní jako dobrý nápad.

Dave Lister:

ahoj,mam otazecku, zda je vhodny "rušit" proměnný tímhle způsobem

<?php
$var1
= $var2 = $var3 = $var4 = $var5 = '';
?>

ikona dgx:

pokud počítate s tím, že to není rušení, ale přiřazení prázdného řetězce (a tedy váš program prázdný řetězec interpretuje jako zrušenou proměnnou), proč ne. Skutečné rušení lze provést takto:

<?php
unset($var1, $var2, $var3, $var4, $var5);
?>

Holmistr:

Ahoj Jakube,

mám tady takový dotaz. Právě jsem na svém develu přešel na PHP 5.3 a jelikož jsem nevěděl o jeho nastaveních na vyhazování E_NOTICE při neinicializovaných proměných, popř. klíču pole, nestačil jsem se divil, jak se mi stránka zahltila výpisy E_NOTICE.

Samozřejmě souhlasím,že je lepší psát kód bez neinicializovaných proměných, což také dodržuji, ovšem byl jsem opravdu překvapen onou vlastností vypsat E_NOTICE na neinicializovaný klíč v poli.

Příklad z praxe
<?php
if($_GET['action']=="zobrazit"){
    //proveď příkazy
}
?>

Při tomto doposud správném kódu mi PHP 5.3 hlásí E_NOTICE v případě, že parametr action neexistuje. Samozřejmě jsem zjednal nápravu v podobě

<?php
if(isset($_GET['action'])){
   if($_GET['action'] == "zobrazit"){
     //proveď příkazy
   }
}

//nebo

if(isset($_GET['action'] && $_GET['action'] == "zobrazit"){
   //proveď příkazy
}

?>

Zajímalo by mě, jestli něco dělám špatně nebo jak to řešíš Ty. Přijde mi to značně nepraktické před každým testováním typicky GET parametrů volat isset()/empty().
Nemůže to mít nějaký vliv na výkon?
Dá se to řešit jinak?
Nebo to mám prostě pustit z hlavy, neboť na produkčních serverech je error_reporting = 0? (toto řešení se mi vůbec nelíbí)

Díky za odpověď a omlouvám se, jestli je otázka moc primitivní.

ikona Jakub Vrána OpenID:

Mně také reportování neexistujících klíčů ve správně inicializovaném poli příliš nevyhovuje. V PHP kódu to obvykle řeším tak, že mám E_NOTICE vypnuté a neinicializované proměnné kontroluji samostatně pomocí http://code.google.com/p/php-initialized/. To má i několik výhod.

Alternativou je využít nějakou abstrakci, díky které se na to příliš často nenarazí – sám používám Nette.

Kontrola nastavenosti proměnné nezabere skoro nic a není tedy na místě tohle rozhodnutí podřizovat výkonu.

Na produkčních serverech je vhodné mít error_reporting nastavený stejně jako při vývoji a změnit pouze display_errors a log_errors.

Holmistr:

Díky za odpověď!

Právě jsem také začal uvažovat, že bych pronikl do tajů Nette, takže snad to vyřeším takto.

ad 4) myslel jsem display_error místo error_reporting :-)

optik:

Určitě bych nesouhlasil s obecným tvrzením, že nastavovat null je nevhodné. Jak poznamenal dgx, naopak u objektových proměnných to tak být musí, protože neexistuje jiná hodnota než null, která by lépe representovala "false" objekt ve smyslu if (!$myObject)

Článek je celý takový divný, nechal ve mě pocit, jakoby
chování isset a var_dump snad bylo v PHP nějaké divné, což není. Isset dělá přesně to, to člověk v 99% očekává a potřebuje. Také neznám nikoho kromě Tebe, kdo by neměl při vývoji puštěné E_NOTICE, a už vůbec bych to nikomu nedoporučoval dělat. Pak var_dump($undefined) normálně hlásí nedefinovanou proměnnou a vše je jak má být.

ikona Jakub Vrána OpenID:

Pokud se null používá jako náhrada za zrušení proměnné, tak je použito špatně – lepší je použít unset(). A pokud se používá pro inicializaci, tak je také obvykle lepší použít rovnou cílový typ. To jsem chtěl tou větou v závěru článku především říct.

U nepovinných objektových parametrů funkcí nic jiného než null samozřejmě použít nejde: <?php function f(stdClass $obj = null) {} ?>. Uveď ale prosím příklad, na kterém by bylo dobře vidět smysluplné použití explicitního přiřazení null do proměnné.

Jestli v tobě článek nechal dojem, že je chování isset() divné, tak to je v pořádku, protože si to skutečně myslím. Podle mě by mi isset() mělo říct skutečně pouze to, jestli je proměnná definovaná. Konstrukce isset() se totiž ve většině případů podle mě používá špatně – viz můj článek http://php.vrana.cz/kdy-pouzit-isset-a-kdy-null.php. A nejsem jediný, komu takové chování chybí: http://www.xarg.org/2011/06/php-hacking/ – funkce exists().

Chování funkce var_dump() je naopak v pořádku – nedefinovaná proměnná se při použití chová jako null (což je podle mě v pořádku), takže var_dump() říká pravdu. V článku jsem jen upozornil na to, že nás neupozorní na rozdíl v nedefinované proměnné a proměnné s hodnotou null.

Chování E_NOTICE mi jednoduše nevyhovuje. Upozorňuje na chyby, které nedělám, a přitom mě nutí psát složitější kód, který program znepřehledňuje: viz http://php.vrana.cz/inicializace-promennych.php. Já také nikomu nedoporučuji programovat bez E_NOTICE – to, že tento druh chyb já osobně nedělám, ještě neznamená, že to nemůže někomu jinému pomoct, takže se mu napsat složitější kód vyplatí.

Při vyčleňování části kódu do funkce (když si potřebuji ověřit, jestli jsem funkci nezapomněl předat nějaký parametr) používám mnohem lepší nástroj než E_NOTICE: http://code.google.com/p/php-initialized/. Lepší je především proto, že se o chybě dozvím bez potřeby spuštění kódu (což je jinak docela problematické, protože neinicializovaná proměnná se může nacházet ve větvi, která se běžně nespouští). Obdobnou funkcí disponují i některé editory, prý např. http://www.jetbrains.com/phpstorm/.

Miloš Brecher:

Já občas používám přiřazení hodnoty NULL do proměnné při inicializaci - např před začátkem cyklu - v jehož těle přiřazuji např TRUE/FALSE. Pak mohu operátorem === zjistit zda se do proměnné něco přiřadilo. Funkci unset() se přiznám téměř nepoužívám - nechávám použité proměnné dožít do konce běhi php skriptu.

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 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.