Door
Jules Kreutzer
op 25-12-2012 23:45
gewijzigd op 25-12-2012 23:46
2.526 views
Wanneer ik me probeer in te loggen, word ik doorgestuurd naar de pagina login.php
Ik krijg dan deze foutmelding te zien:
Warning: Invalid argument supplied for foreach() in blahblahblah
De error zit op lijn 28
Het bestand login.php ziet er als volgt uit:
<?php
require 'login-libs.php';
require 'connect.php';
login_check_is_email_provided();
// check that the password is provided
if(!isset($_REQUEST['password']) || $_REQUEST['password']==''){
login_redirect($url,'nopassword');
}
// check that the email/password combination matches a row in the user table
$password=md5($_REQUEST['email'].'|'.$_REQUEST['password']);
$r= 'select * from user_accounts where
email="'.addslashes($_REQUEST['email']).'" and
password="'.$password.'"'
;
$result=mysql_query($r);
if($result==true){
login_redirect($url,'loginfailed');
}
// success! set the session variable, then redirect
$_SESSION['userdata']=$result;
$groups=json_decode($result['groups']);
$_SESSION['userdata']['groups']=array();
foreach($groups as $g)$_SESSION['userdata']['groups'][$g]=true;
if($result['extras']=='')$result['extras']='[]';
$_SESSION['userdata']['extras']=json_decode($result['extras']);
login_redirect($url);
?>
>> Invalid argument supplied for foreach
Vrij vertaalt: "Er is een verkeerd argument gegeven aan de foreach"
We weten dus dat het een verkeerd argument betreft, we moeten dus kijk wat er straks tussen () staat.
2) De regel opzoeken
Oké, we weten nu de error opzoek naar de regel:
<?php
foreach($groups as $g)$_SESSION['userdata']['groups'][$g]=true;
?>
Oef, eerst even normalizeren:
<?php
foreach ($groups as $g) {
$_SESSION['userdata']['groups'][$g] = true;
}
?>
Oké, iets in ($groups as $g) is verkeerd. Het moet dus $groups zijn die verkeerd is.
3) Informatie verzamelen
Laten even informatie gaan verzamelen, hoe wordt $groups bijvoorbeeld aangemaakt?
<?php
$groups = json_decode($result['groups']);
?>
Hmm, dat geeft niet veel informatie.
4) Je verwachtingen controleren
Je verwacht dat $groups een array is, anders stop je hem niet in je array. Laten we die gedachten eens controleren door de [php]assert[/php] functie te gebruiken, hiermee kun je aangeven dat je iets verwacht zoniet dan krijg je een USER_WARNING op je scherm:
<?php
// ...
$groups = ...;
assert('is_array($groups)');
?>
Ik verwacht dat je hier een warning voor gaat krijgen, de $groups bevat dus geen array, hmm....
5) De documentatie doorlezen
Laten we dan eens gaan kijken in de documentatie over die functie: [php]json_decode[/php]
Het eerste wat mij al opvalt is de 'mixed' in het functie-voorbeeld in het 'description' blok. Hij hoeft dus niet per se een array terug te geven.
Vervolgens kijken we in het 'Return values' blok, aangezien dat het geen is dat we willen weten:
Returns the value encoded in json in appropriate PHP type. (...) NULL is returned if the json cannot be decoded or if the encoded data is deeper than the recursion limit.
Hmm, hij kan dus ook NULL returnen. Laten we dan eens gaan kijken of hij NULL terug heeft gegeven:
<?php
// ...
$groups = ...;
assert('is_null($groups)');
?>
Hoogstwaarschijnlijk krijg je hier geen error over, de waarde is dus NULL. Dan kun je dit debug proces weer herhalen om erachter te komen wat er fout is (is het een ongeldige jSON string? Is het recursie limit te hoog?)
In elk geval zit er 1 echte fout op Regel 20. $result is de uitkomst van een select query. Dat is ofwel een resource, ofwel false als de query is mislukt, het kan in elk geval nooit true zijn.
Verder lijkt het erop dat je een json string uit je database gaat krijgen hier. Dat riekt ook naar een erg slecht ontworpen database.
Volgens mij kan je beter het advies dat Ger in een ander topic al gaf ter harte nemen....
Ik heb de warning kunnen wegwerken (inclusief een paar andere) door middel van volgende code aan te passen:
<?php $r= 'select * from user_accounts where
email="'.addslashes($_REQUEST['email']).'" and
password="'.$password.'"'
;
?>
naar:
<?php
$r= 'select * from user_accounts where
email="'.addslashes($_REQUEST['email']).'" and
password="'.$password.'" and groups = ["_superadministrators"] OR ["_administrators"]'
;
?>
Wanneer ik nu op login klik, krijg ik echter wel nog de link redirect te zien. wanneer ik daar op klik, zou ik normaal op index.php moeten uitkomen, maar hij laat toch nog steeds het loginscherm zien. Dus de sessies zijn niet gezet...
bij login.php wordt verwezen naar login-libs.php (waar ook de redirect link wordt gemaakt). Dit bestand ziet er zo uit:
<?php
require 'basics.php';
$url='/';
$err=0;
function login_redirect($url,$msg='success'){
if($msg)$url.='?login_msg='.$msg;
header('Location: '.$url);
echo '<a href="'.htmlspecialchars($url).'">redirect</a>';
exit;
}
// set up the redirect
if(isset($_REQUEST['redirect'])){
$url=preg_replace('/[\?\&].*/','',$_REQUEST['redirect']);
if($url=='')$url='/';
}
// check that the email address is provided and valid
function login_check_is_email_provided(){
if(
!isset($_REQUEST['email']) || $_REQUEST['email']==''
|| !filter_var($_REQUEST['email'], FILTER_VALIDATE_EMAIL)
){
login_redirect($GLOBALS['url'],'noemail');
}
}
?>
Ik heb de warning kunnen wegwerken (inclusief een paar andere) door middel van volgende code aan te passen:
<?php $r= 'select * from user_accounts where
email="'.addslashes($_REQUEST['email']).'" and
password="'.$password.'"'
;
?>
naar:
<?php
$r= 'select * from user_accounts where
email="'.addslashes($_REQUEST['email']).'" and
password="'.$password.'" and groups = ["_superadministrators"] OR ["_administrators"]'
;
?>
Als je exit() helemaal aan het begin van je script zet ben je ook van alle warnings af....
Dit lost geen enkel probleem op. Wat je nu hebt is een fout SQL statement, waardoor je query gewoon mislukt en je script dus zal afbreken. Ja, je bent dan die php warning kwijt, maar inloggen zal je nooit meer lukken.
Waar het om gaat, in het oorspronkelijke probleem, is dat er in de database blijkbaar een verkeerde waarde staat in de kolom groups en dat dien je gewoon te controleren voor je verder gaat. Dus voor je die foreach in gaat moet je controleren of $groups wel een array is (en dat doe je met de functie [php]is_array[/php]). Is het geen array dan kan je gewoon die foreach niet uitvoeren.
Ik heb de code weer terug naar het orginele gezet en ben in mijn database de waarde van group gaan veranderen.
Bij de voorbeeldgebruiker, staat er bij groups: ["_superadministrators"]. dit heb ik veranderd bij mijn account naar _superadministrators. Dit heeft echter geen effect.
De index.php pagina vraagt naar de sessie waar de groups gelijk is aan superadministrators of administrators, is dat niet het geval, wordt het login formulier getoont.
Het bestand wat controleert of de group goed is, heet admin_libs.php en ziet er als volgt uit:
Gebruik liever geen $_REQUEST, beter is om expliciet te zeggen welk type parameter je wilt $_GET of $_POST of $_SESSION.
Tevens raad ik je aan je database te normalizeren. Zodra je een array-achtige waarde in een cell hebt staan weet je dat het verkeerd is. Hoe ik het zou doen:
Bij de voorbeeldgebruiker, staat er bij groups: ["_superadministrators"]. dit heb ik veranderd bij mijn account naar _superadministrators. Dit heeft echter geen effect.
Ook dat lijkt me niet verstandig. Wat je dan aan het doen bent is een klein onderdeel van een groter geheel veranderen. Het grotere geheel zou echter als geheel moeten werken. Als dat het niet doet moet je niet lukraak iets veranderen, maar eerst begrijpen hoe het werkt.
Uit alles wat je verteld heb begrijp ik dat er een json encoded array in een database veld staat (in groups). Als je daar nu een normale waarde van maakt, dan zal je json_decode misgaan en daarmee ook je foreach loop, want je krijgt er namelijk geen array meer uit. Waar de fout dan wel ligt, geen idee.
Wat ik wel kan zeggen is dat ik dit script al veel eerder in de prullenbak zou hebben gegooit. Het staat bol van de fouten of in elk geval verkeerde dingen. Vooral het gebruik van json encoded gegevens in een database, waar je (zoals Wouter helemaal terecht opmerkt) een genormalizeerde structuur zou moeten toepassen is echt een grote no-no.
Ik heb momenteel een tabel user_account waar de kolom group de waarde bevat zoals ["_superadministrators"] en er is een tabel groups waar name de waarde _superadministrators of _administrators bevat, met respectivelijk id 1 en 2. Dit is toch wat je bedoelt wouter of niet?
[size=xsmall]Toevoeging op 26/12/2012 14:02:45:[/size]
Het is opgelost,
Wanneer ik het op mijn locale host teste kreeg ik de http-500 error, ik heb dat opgelost (geprobeerd in ieder geval) door $r om te zetten naar $result.
Maar wanneer ik op mijn webserver het orginele bestand upload, werkt het wel, en is het ook mogelijk om ingelogd te worden.
orginele login.php:
<?php
require 'login-libs.php';
login_check_is_email_provided();
// check that the password is provided
if(!isset($_REQUEST['password']) || $_REQUEST['password']==''){
login_redirect($url,'nopassword');
}
// check that the email/password combination matches a row in the user table
$password=md5($_REQUEST['email'].'|'.$_REQUEST['password']);
$r=dbRow('select * from user_accounts where
email="'.addslashes($_REQUEST['email']).'" and
password="'.$password.'" and active'
);
if($r==false){
login_redirect($url,'loginfailed');
}
// success! set the session variable, then redirect
$_SESSION['userdata']=$r;
$groups=json_decode($r['groups']);
$_SESSION['userdata']['groups']=array();
foreach($groups as $g)$_SESSION['userdata']['groups'][$g]=true;
if($r['extras']=='')$r['extras']='[]';
$_SESSION['userdata']['extras']=json_decode($r['extras']);
login_redirect($url);
?>