Jak psát kód: Inicializujte proměnné
Vzhledem k tomu, že proměnné není třeba v PHP deklarovat, tak je někdo ani neinicializuje. Jde o zásadní chybu, která může způsobit vážné problémy. PHP sice nabízí částečnou ochranu v podobě chybové úrovně E_NOTICE
, ta ale nevaruje před přidáním prvku do neinicializovaného pole. Na to doplatíme třeba v následujícím kódu:
<?php
foreach ($results as $result) {
$item['id'] = $result['id'];
foreach ($result['children'] as $key => $child) {
$item['children_ids'][$key] = $child['id'];
}
// Tady něco uděláme s $item.
}
?>
První iterace proběhne v pořádku, některé další možná taky, PHP neoznámí žádnou chybu. Ale pokud má prvek míň dětí než kterýkoliv předchozí, tak do něj prosáknou děti z předchozích prvků. Chybu lze opravit přidáním $item = array();
na začátek iterace a ideálně i $item['children_ids'] = array();
. Pro nalezení této chyby je vhodné použít externí nástroj. K této chybě může dojít v mnoha variantách, častá je např. také tato:
<?php
function setFromData(array $data) {
$this->name = $data['name'];
if (!empty($data['enabled'])) {
// V opačném případě zůstane $this->enabled původní hodnota. Správně by se mělo přiřadit false.
$this->enabled = true;
}
}
?>
Přijďte si o tomto tématu popovídat na školení Bezpečnost PHP aplikací.
Diskuse
+1. Inicializace proměnných patří k základům dobrého programování.
29.5.2013 09:35:27
Lze tu chybu v setFromData nějak strojově detekovat. Podle článku bych skoro řekl, že ano, ale IMHO to je dost velký problém. Stroj nemá moc možností, jak vhodně odhadnout, jestli je toto chování záměr.
Samozřejmě, chybu bych řešil řádkem
<?php $this->enabled = !empty($data['enabled']); ?>.
29.5.2013 11:08:04
Tuto chybu skutečně detekovat nelze (pokud je $this->enabled deklarováno).
29.5.2013 11:39:00
Díky za odpověď.
Mimochodem,
http://www.vrana.cz/php-initialized/ to nedetekuje ani bez deklarovaných enabled a name. Je to záměr, nebo chyba?
Vím, že u dynamicky typovaných jazyků to není úplně snadné, ale aspoň u $this by to šlo...
29.5.2013 21:13:19
php-initialized kontroluje bohužel jenom proměnné. Vlastnosti objektů ani klíče pole nekontroluje.
30.5.2013 21:20:27
Michal:
Škoda, mám jeden velký projekt, na který bych to hned pustil. Ale život jde dál :-)
30.5.2013 22:20:13
Ahoj Jakube,
chtěl jsem se tě zeptat, jaký je tvůj názor na "inicializaci" vlastností ve třídě.
Může se nechat
<?php
class Example {
public $foo;
}
?>
nebo je lepší
<?php
class Example {
public $foo = NULL;
}
?>
29.5.2013 12:01:54
Není v tom rozdíl, hodnotu NULL bude mít vlastnost foo v obou případech. Pokud vím, nelze toto ani detekovat (jestli už byla přiřazena hodnota, i kdyby byla NULL).
29.5.2013 13:17:07
Hodnotu null vlastnostem explicitně nepřiřazuji, protože je implicitní.
29.5.2013 13:32:39
František Musil:
Stalo by mozna za zminku, ze kazda neinicializovana promenna zpomaluje kod. Provadel jsem na jednoducchy test a ve vysledku to na vetsim kodu to nejaka ta ms je (byt jde o jednotky). Nemluve o tom, ze je kod citelnejsi a clovek se pak dopousti mene chyb. Dale odpadaji problemy jako: isset($data) && !empty($data). Nicmene jeste jedna zkusenost: pokud ladujete viceurovnove pole a je to v cyklech tak zamotane, ze by inicializace znamenala podminku, tak se to casove neoplati a prehledne to taky neni. PHP pro stesti staci, kdyz bude vedet, ze zakladni promenna je array. Nicmene pak riskujete vyse zminene.
Diky za moznost sem napsat neco hodnotneho :-)
29.5.2013 13:39:30
František Musil:
Mel bych souvisejici dotaz. Doporucujete vychozi hodnoty deklarovat primo pri inicializaci (konkretne array()) nebo v constructoru? Pripadne proc.
29.5.2013 15:24:53
Rovnou v deklaraci: private $data = array(). Je z toho hned vidět typ dat, navíc se ušetří řádek. Kdysi jsem někde četl, že není vhodné takhle inicializovat obří hodnoty, protože PHP zabere paměť dvakrát, ale obří hodnoty neinicializuji. Navíc už to neplatí – od PHP 5.3 to zabere stejně paměti a v PHP 5.2 to je dokonce naopak.
29.5.2013 19:51:30
František Musil:
Dekuji :-)
29.5.2013 20:27:39