Count a isset bug v PHP

Dnes jsem narazil (ještě teď mě bolí hlava) na celkem zajímavý bug kolem proměnných v PHP. Mordoval jsem se s tím skoro půl hodiny...

Měl jsem rozsáhlé přibližně 4 rozměrné pole, které mělo až na poslední dva prvky pravidelnou strukturu. A ty poslední dva prvky mi pořádně zamotaly hlavu.

$snih = array ( 0 => array('Hory'=>'Krkonose',
			   'Mista'=>array( 0 => array('misto'=>'Pec',
			                              'snih'=>45
                                                      ),
                                           1 => array('misto'=>'Cerna hora',
                                                      'snih'=>20
						      )
				          ),
			   ),
		1 => array('Hory'=>'Jeseniky',
		           'Mista'=>array( 0 => array('misto'=>'Ramzova',
					              'snih'=>80
						      ),
					    1 => array('misto'=>'Serak',
					               'snih'=>120
						       )
					  ),
			   ),

		'date' => '19.6.1986',
		'time' => '22:58'
	      );

foreach($snih as $hory){
	echo $hory['Hory'];
	foreach($hory['Mista'] as $misto){   
		//....
	}
}
Výstup:
KrkonoseJeseniky12
Kde se tam vzala ta 12? Pochopil jsem záhy, že za to mohou poslední dva prvky pole a chtěl jsem tyto dva prvky při procházení polem odfiltrovat. Zkusil jsem proto klasiku: count a isset. Nepomohlo...
foreach($snih as $hory){
	if (!(isset($hory['Mista']) && 
              count($hory['Mista']) < 1)) continue;
	echo $hory['Hory'];
	foreach($hory['Mista'] as $misto){   
		//....
	}
}

V čem je bota?

Řetězec "19.6.1986" se umístí do proměnné $hory. Ve chvíli kdy se vypisuje $hory['Hory'], vypíše PHP první znak řetězce, tedy "1". To by se dalo ještě jakž takž považovat za korektní chování. Horší je to ovšem s funkcí count. Ta, přestože předaný parametr není pole, vrátí 1.

bug report funkce count

Stejně podivně se chová i funkce isset. Vrací jedna, přestože žádný takový index neexistuje.

Naštěstí ale řešení existuje:

foreach($snih as $hory){
	if (!is_array($hory['Mista'])) continue;
	echo $hory['Hory'];
	foreach($hory['Mista'] as $misto){   
		//....
	}
}

evaluation

comments

[1] vítek
2007-11-13 18:19:16

prvky zamotalY

replied by [4] Dundee
2007-11-14 00:02:18

Asi takle - v podstate se nejedna o zadne bugy. Nejprve musim rict k tomu "12", ze kdyz si das vypsat prvek $snih['date']['Hory'], vyhodnoti se 'Hory' jako 0 (protoze kazdy predpoklada, ze pole nese konzistentni data a kdyz nekdo (tj. programator)zazada o neexistujici klic, vyhodnoti se vyraz (v tomto pripade "pseudo")logicky) a 0. index retezce je prvni znak, tedy "1" a "2". Jasne - neni to zrovna neco, cim by se PHP mohlo chlubit, ale nejdriv se podivej na duvody neshody poradne a az potom se pripadne ozvy.

U fci count a isset se to ma nasledovne - Fce isset (z vyse uvedeneho) vraci 1 (protoze i jednoprvkove pole je pole). Ted to bude orisek: "Kde jsi vzal promennou $item, kterou jsi pouzil jako parametr fce count???" Je prirozene, ze PHP neexistujici promennou akceptuje (s warningem) a count vrati 0 (kolik prvku by take jinak melo mit "pole" NULL?) a nasledne vyraz !(1 && 0) == 1

Tedy jestli tu byl nejaky bug, tak to bylo to, co jsi uznal za pochopitelne :)

2007-11-14 00:24:04

(blby preklep :D ... nasleduje oprava a pokracovani)
...a count vrati 0 a nasledne vyraz !(1 && 0 < 1) == 0.
Ve spravne podobe ( count($hory['Mista']) < 1 ) ti bude vychazet (z totozneho duvodu, jak u isset) vyraz !(1 && 0) == 1, coz take neni to prave, protoze ti zdrojak nic nevytiskne.
Problem je v tom, ze PHP implicitne meni datovy typ promennych podle potreby. Takze resenim je prave pouziti testu datoveho typu is_array, jestli ne/odpovida tomu, co ocekavame.

Shrnuti - To, co povazujes za bug, je jen chyba ze tve strany. Navrhoval bych tedy odstraneni tohoto clanku.

replied by [4] Dundee
[4] Dundee
2007-11-14 01:31:51

#1 vítek: Diky

#3 TOP-Tom: item=hory(preklep)

Vypsani "12" opravdu primo bug neni. Je to jen zajimavost. Co se tyce count a isset, tam uz je situace trochu jina. Count totiz vraci 1 a ne 0, jak ty pises...vyzkousej si to. Sice to mozna take neni primo bug, ale je to velice nelogicke chovani.

To same plati pro isset.

Obe funkce proste vraceji neco, co od nich uzivatel ani v nejmensim neocekava. A to je dle me chyba.

Nerekl bych, ze se nejedna ani v jednom pripade o bug, kdyz je to v bugs reportu na php.net

[5] Dundee
2007-11-14 01:41:17

Je sice pravda, ze v dokumentaci funkce count je toto chovani explicitne uvedeno, stale si ale myslim, ze se nejedna o spravnou implementaci. Kratky popis prece zni:

"Count elements in an array, or properties in an object."

Takze je normalni ocekavat, ze v pripade, ze se funcki preda neco jineho nez iterable objekt nebo pole, vyhodi funkce vyjimku / vrati 0 nebo -1.

Osobne mi to proste prijde logictejsi...

[6] Dundee
2007-11-14 01:44:49

Stejnetak nechapu proc by isset mela vracet 1. V pripade, ze se promenna pretypuje na pole, index existovat nebude a isset ma vratit 0.

Zde to vyplyva z toho ze "fgfgh"['cokoliv'] = "f"

takze ten index jakoby existuje.

2007-11-14 10:51:31

Napsal jsem to blbe, pardon :D Uz jsem byl trochu unaveny.

Takze jeste jednou: Fce count u "nepole" vraci 1, protoze count se podive na promennou (resp. na jeji NULTY PRVEK v nasem pripade), pretypuje si ji pro sve ucely na pole a rika si: "Aha, tohle pole ma jen jeden prvek", a tedy vyraz "1 < 1" se vyhodnoti jako 0. Kod se potom vyhodnocuje takto:

!(1 && 2 < 1) == 1

!(1 && 2 < 1) == 1

!(1 && 1 < 1) == 1

!(1 && 1 < 1) == 1

A toto "velmi nelogicke chovani" je jen primym dusledkem prave te zajimavosti "12" - diky ni isset i count vraci 1. Tedy podle meho, jestli se jedna o bug, melo by se upzornovat na "12", nikoli na samotne fce isset, count - ty za nic nemohou, protoze se chovaji presne tak, jak se chovat maji.

A snad uz jsem se zas nekde nezmylil :)

[8] Dundee
2007-11-14 15:51:49

#8#Jasne. chovani isset i count primo vyplyva z te "zajimavosti". Bohuzel to pak dost narusuje ocekavane chovani obou funkci. Je to holt jakasi dan za naprostou typovou volnost...

comments closed