Avatar billede Slettet bruger
28. marts 2011 - 15:40 Der er 20 kommentarer og
1 løsning

MySQLi - Kan det virkelig være rigtigt ?!?

Det sku' jo være så godt med *i og prepared statements : )

Opslag med nøgle ($eMail) for at hente $UiD + $PW fra tabellen USR:

require "DB_CONST.php";

if (! $link = mysqli_connect(DB_URL, DB_USER, DB_PW, DB_BASE))
    die("Databasefejl (connect): ".mysqli_connect_errno()." ~ ".mysqli_connect_error()."</body></html>");

if (! $stmt = mysqli_prepare($link, "SELECT UiD,PW FROM USR WHERE eMail=?"))
    {
    mysqli_close($link);
    die("Databasefejl (prepare): ".mysqli_errno()." ~ ".mysqli_error()."</body></html>");
    }   

if (! mysqli_stmt_bind_param($stmt, "s", $eMail))
    {
    mysqli_stmt_close($stmt);
    mysqli_close($link);
    die("Databasefejl (bind_param): ".mysqli_errno()." ~ ".mysqli_error()."</body></html>");
    }
   
if (! mysqli_stmt_execute($stmt))
    {
    mysqli_stmt_close($stmt);
    mysqli_close($link);
    die("Databasefejl (execute): ".mysqli_errno()." ~ ".mysqli_error()."</body></html>");
    }

if (! mysqli_stmt_bind_result($stmt, $UiD, $PW))
    {
    mysqli_stmt_close($stmt);
    mysqli_close($link);
    die("Databasefejl (bind_result): ".mysqli_errno()." ~ ".mysqli_error()."</body></html>");
    }

if (! mysqli_stmt_store_result($stmt))
    {
    mysqli_stmt_close($stmt);
    mysqli_close($link);
    die("Databasefejl (store_result): ".mysqli_errno()." ~ ".mysqli_error()."</body></html>");
    }

if (1 == ($antal = mysqli_stmt_num_rows($stmt)))
    {
    if (!mysqli_stmt_fetch($stmt))
        {
        mysqli_stmt_free_result($stmt);
        mysqli_stmt_close($stmt);
        mysqli_close($link);
        die("Databasefejl (fetch): ".mysqli_errno()." ~ ".mysqli_error()."</body></html>");
        }
    }
mysqli_stmt_free_result($stmt);
mysqli_stmt_close($stmt);
mysqli_close($link);

if ($antal == 0)
    die("Fandt ingen user med eMail = $eMail</body></html>");

if ($antal > 1)
    die("Mere end 1 user med denne eMail ($eMail) ?!?</body></html>");
   

echo "Pyha! Fandt($eMail) => UiD=$UiD og PW:$PW";


Det virker fint, men..
- der ER temmelig mange linjer, er der ikke ?
(også selvom jeg samler de mange fejl-udskrifter i én function)
Avatar billede Slettet bruger
28. marts 2011 - 16:10 #1
Skrumpet version:

$link = null; $stmt = null;

function brokUd($hint,$eNum,$eTxt)
    {
    if ($stmt)
        {
        mysqli_stmt_free_result($stmt);
        mysqli_stmt_close($stmt);
        }
    if ($link)
        mysqli_close($link);
    die("Databasefejl ($hint): $eNum ~ $eTxt </body></html>");
    }
   
require "DB_CONST.php";

if (! $link = mysqli_connect(DB_URL, DB_USER, DB_PW, DB_BASE))
    brokUd("connect",            mysqli_connect_errno(), mysqli_connect_error());

if (! $stmt = mysqli_prepare($link, "SELECT UiD,PW FROM UsSR WHERE eMail=?"))
    brokUd("prepare",            mysqli_errno($link), mysqli_error($link));

if (! mysqli_stmt_bind_param($stmt, "s", $eMail))
    brokUd("bind_param",    mysqli_errno($link), mysqli_error($link));
   
if (! mysqli_stmt_execute($stmt))
    brokUd("execute",            mysqli_errno($link), mysqli_error($link));

if (! mysqli_stmt_bind_result($stmt, $UiD, $PW))
    brokUd("bind_result",    mysqli_errno($link), mysqli_error($link));

if (! mysqli_stmt_store_result($stmt))
    brokUd("store_result",    mysqli_errno($link), mysqli_error($link));

if (1 == ($antal = mysqli_stmt_num_rows($stmt)))
    if (! mysqli_stmt_fetch($stmt))                                           
        brokUd("fetch",            mysqli_errno($link), mysqli_error($link));
   
mysqli_stmt_free_result($stmt);
mysqli_stmt_close($stmt);
mysqli_close($link);

if ($antal == 0)
    die("Fandt ingen user med eMail = $eMail");

if ($antal > 1)
    die("Mere end 1 user med denne eMail ($eMail) ?!?");
   

echo "Pyha fandt ($eMail) => UiD=$UiD og PW:$PW";

Stadig ganske velvoksen... Er det bare sådan det ER ?
Avatar billede Slettet bruger
28. marts 2011 - 16:29 #2
Rettelse:
function brokUd( $hint, $eNum, $eTxt )
    {
    global $link, $stmt;
    if ($stmt)
        {
        mysqli_stmt_free_result($stmt);
        mysqli_stmt_close($stmt);
        }
    if ($link)
        mysqli_close($link);
    die("Databasefejl ($hint): $eNum ~ $eTxt </body></html>");
    }


Men ELLERS : )
Avatar billede arne_v Ekspert
29. marts 2011 - 00:33 #3
1) Hvis du laver det paa den gammeldags maner bliver det ogsaa til en del linier naar der skal testes for fejl.

2) Der skal testes for fejl.

3) Antal linier betyder ikke ret meget. Robust kode som er nem at vedligeholde betyder noget. Husk at i professionel software udvikling skrives der ca. 250-500 linier kode OM MAANEDEN. At taste 10 linier mere ind her og der betyder ikke noget.

4) Brug af mysqli_stmt_bind_result er ikke noedvendigt for at beskytte mod SQL injection (maxdb_stmt_bind_param er noedvendig).

5) Kig evt. lidt paa PDO - ofte kan man lave det samme med PDO (stadig med parameters) som i mysqli men lidt simplere.

6) Aldrig aldrig aldrig global!
Avatar billede Slettet bruger
29. marts 2011 - 01:26 #4
Jeg satser på at indkapsle de mange linjer, og kalde det hele med noget der ligner:

$resultat = getData("SELECT snik, snak FROM tabel WHERE xyz=? and qwe=?", $parms);

Hvor $parms er et array af strenge (opslagsnøgler i samme rækkefølge som ?'ene i SQL'en).
Og $resultat er enten FALSE eller et 2-dimensionelt array med alle rows i ét hug : )

Jeg er dog lidt i tvivl om:

Giver det mening at bruge store_result - php.net er lidt på den ene siden og lidt på den anden: memmory eller hastighed...
- Hvornår er det en fordel (mange rows (hvor mange?)) og hvornår er det spildt (kun en row?)


Kan man være SIKKER på at parameter-binding forhindrer SQL-injections ?
(Oversat: slipper JEG for at "sanity-checke" input ?)


Og endelig: global - hvorfor ikke (det er ikke SUPERGLOBAL) - blot $link (connection'en)
- jeg kunne selvfølgelig bare overføre den som parameter, men hvad er risikoen ved global ?!?
Avatar billede arne_v Ekspert
29. marts 2011 - 02:39 #5
Ja - parameter beskytter mode SQL injection.

Nej - du skal stadig checke input - der er andre potentielle risici såsom XSS.

global er ikke en sikkerheds risiko men gør det sværere at læse koden.
Avatar billede arne_v Ekspert
29. marts 2011 - 02:40 #6
Jeg har ikke erfaring nok til at kunne udtale mig om store result.
Avatar billede arne_v Ekspert
29. marts 2011 - 02:45 #7
Med den getData funktion tror jeg bestemt at du vil blive glad for PDO fremfor mysqli!
Avatar billede Slettet bruger
29. marts 2011 - 10:59 #8
Super, tak for gode (ikke-obstruktive) svar - jeg har lige ET par mere:

Eksempel:
      mysqli_prepare($link, "SELECT navn FROM gruppe WHERE id=?"); //id = INT(20)
      $key = 4711;  //Også integer
      mysqli_stmt_bind_param($stmt, "s", $key);  //Altså s=string..
Virker helt fint, tilsyneladende.. ?
Det tolker jeg straks: Jeg kan behandle alle parametre som "s" !
Hvilket jo forenkler min getData()-funktion voldsomt - men ER det et skråplan ?


PDO - hvad er fordelen ?
- Udover at gøre det lettere at skifte den underliggende database, pyt.
Bagdelen (i praksis): Kun ganske (og "atomiserede") eksempler på php.net..
- jeg har endnu ikke fundet et komplet, fra "connect" til data er vel tilbage i $php..
Avatar billede arne_v Ekspert
29. marts 2011 - 14:59 #9
Jeg ved ikke praecis hvad der sker i det eksempel. MySQL er generelt ret afslappet med hensyn til typer.

WHERE v = '123'

virker fint selvom v er en INT.

Jeg ville goere det rigtigt. Just in case at de strammede lidt op.
Avatar billede arne_v Ekspert
29. marts 2011 - 15:01 #10
PDO er database uafhaengigt og du ved ikke om du faar lyst til at skifte database om 5 aar.

Derudover giver PDO efter min mening lidt paenere/simplere kode i nogle tilfaelde. Men proev det og drag dine egne konklusioner.

Lidt blandede eksempler:

<?php
// standard
echo "standard:<br>\n";
$db = new PDO('mysql:host=localhost;dbname=Test');
$stmt = $db->prepare("SELECT * FROM T1");
$stmt->execute();
while($row = $stmt->fetch()) {
    $f1 = $row['F1'];
    $f2 = $row['F2'];
    echo "$f1 $f2 <br>\n";
}
$stmt->closeCursor();
// short form
echo "short form:<br>\n";
$db = new PDO('mysql:host=localhost;dbname=Test');
$stmt = $db->prepare("SELECT * FROM T1 WHERE F1 > :F1");
$stmt->execute(array(':F1' => 2));
while($row = $stmt->fetch()) {
    $f1 = $row['F1'];
    $f2 = $row['F2'];
    echo "$f1 $f2 <br>\n";
}
$stmt->closeCursor();
// bindValue
echo "bind value:<br>\n";
$i = 2;
$db = new PDO('mysql:host=localhost;dbname=Test');
$stmt = $db->prepare("SELECT * FROM T1 WHERE F1 > :F1");
$stmt->bindValue(':F1', $i);
$i = 4;
$stmt->execute(); // find all >2
while($row = $stmt->fetch()) {
    $f1 = $row['F1'];
    $f2 = $row['F2'];
    echo "$f1 $f2 <br>\n";
}
$stmt->closeCursor();
// bindParam
echo "bind param:<br>\n";
$i = 2;
$db = new PDO('mysql:host=localhost;dbname=Test');
$stmt = $db->prepare("SELECT * FROM T1 WHERE F1 > :F1");
$stmt->bindParam(':F1', $i);
$i = 4;
$stmt->execute(); // find all >4
while($row = $stmt->fetch()) {
    $f1 = $row['F1'];
    $f2 = $row['F2'];
    echo "$f1 $f2 <br>\n";
}
$stmt->closeCursor();
// bind column
echo "bind column:<br>\n";
$db = new PDO('mysql:host=localhost;dbname=Test');
$stmt = $db->prepare("SELECT * FROM T1");
$stmt->execute();
$stmt->bindColumn('F1', $f1);
$stmt->bindColumn('F2', $f2);
while($row = $stmt->fetch()) {
    echo "$f1 $f2 <br>\n";
}
$stmt->closeCursor();
?>
Avatar billede Slettet bruger
29. marts 2011 - 17:08 #11
Ja, det ligner jo mysqli ret meget - må jeg liige overveje..

I mellemtiden - jeg er rigtig godt igang med min getData() nu "runSQL()"

Men i bind_param oplever jeg noget meget mystisk (?):
Hvis jeg angiver en ikke-eksisterende tabel i en SELECT => error: Table x doesn't exist
Hvis jeg angiver en ikke-eksisterende tabel i en INSERT => ingen error
- jeg får også lov at køre execute (også uden fejl) men derefter siger affected_rows() 0
(samme kode virker perfekt hvis tabellen eksisterer)

Er det den forventede opførsel ?!?
Avatar billede Slettet bruger
29. marts 2011 - 17:24 #12
Hov stop - det passer ikke!
"0" kom ud fordi jeg "rapporterede" det med echo "fejl: "+$fejl;

+ i stedet for .

suk.
Avatar billede arne_v Ekspert
29. marts 2011 - 17:25 #13
Det lyder lidt mystisk.

Jeg ved ikke om det er tilsigtet.
Avatar billede Slettet bruger
29. marts 2011 - 18:56 #14
Tadaa, runSQL()
<?php
/*

Brug:
    $resultat = runSQL($sql, $parms, $debug=false);

    $sql    SQL sætning med ? på parametres plads ~ "SELECT * FROM tabel WHERE id=?"
    $parms    Array af typer og parameter-værdier (om nogen)
    $trace    lever (også) callStackTrace ved fejl


retur værdi:
    int:    antal berørte rækker (INSERT/UPDATE/DELETE/..)
    array:    ALLE rækker (måske 0) returneret af SELECT ~ [0]['nr'],[0]['navn']  [1]['nr'],[1]['navn']
    string:    DatabaseFejl # errorNo: errorDescription + (if $debug) <== callStackTrace


Eksempler:
runSQL("SELECT nr,navn FROM table WHERE id = ?",array('i', $id),true);
runSQL("SELECT * FROM table",array());
runSQL("INSERT INTO table(id, name) VALUES (?,?)",array('ss', $id, $name));
runSQL("UPDATE table SET Navn=? WHERE Navn=?",array("ss","mugge","mogens"));
   
*/

require_once("db_CONSTS.php");

function runSQL($sql, $parms, $debug=false)
    {
    $dataRetur = (substr(strtolower($sql),0,6) == "select") ? true : false;
   
    $errorRep = ini_set('display_errors',false); // Min mund er lukket

    $mysqli = new mysqli(DB_URL, DB_USER, DB_PW, DB_BASE);
    if (mysqli_connect_errno()) // proceduralt af kompabilitetshensyn (pre PHP 5.3)
        {
        ini_set('display_errors', $errorRep);
        return "DatabaseFejl #" . mysqli_connect_errno() . ": " . mysqli_connect_error()
                                . (($debug) ? backTrace( debug_backtrace() ) : "");
        }

    if (! ($stmt = $mysqli->prepare($sql)))
        {
        ini_set('display_errors', $errorRep);
        $ers = "DatabaseFejl #" . $mysqli->errno . ": "
                                . $mysqli->error . (($debug) ? backTrace( debug_backtrace() ) : "");
        $mysqli->close();
        return $ers;
        }

    call_user_func_array(array($stmt, 'bind_param'), refValues($parms));

    if (! ($stmt->execute()))
        {
        ini_set('display_errors', $errorRep);
        $ers = "DatabaseFejl #" . $mysqli->errno . ": " . $mysqli->error
                                . (($debug) ? backTrace( debug_backtrace() ) : "");
        $stmt->close();
        $mysqli->close();
        return $ers;
        }

    ini_set('display_errors', $errorRep);

    if (!$dataRetur)
        $result = $mysqli->affected_rows;
    else
        {
        $results = array();
           
        $meta = $stmt->result_metadata();

        while ( $field = $meta->fetch_field() )
            $values[] = &$row[$field->name];

        call_user_func_array(array($stmt, 'bind_result'), refValues($values));

        while ( $stmt->fetch() )
            {
            $x = array(); 
            foreach( $row as $key => $val )
                $x[$key] = $val; 
            $results[] = $x; 
            }
        $result = $results;
        }
    $stmt->close();
    $mysqli->close();
    return  $result;
    }

function refValues($arr)
    {
    if (strnatcmp(phpversion(),'5.3') >= 0) //Reference is required for PHP 5.3+
        {
        $refs = array();
        foreach($arr as $key => $value)
            $refs[$key] = &$arr[$key];
        return $refs;
        }
    return $arr;
    }
   
function backTrace($debug) // kald trace(debug_backtrace())
    {
    $trace = "";
    foreach( $debug as $k=>$v)
        if($v['function'] == "include" || $v['function'] == "include_once" || $v['function'] == "require_once" || $v['function'] == "require")
            $trace .= " <== ".$v['function']."(".$v['args'][0].") kaldt fra ".$v['file'].":".$v['line'];
        else
            $trace .= " <== ".$v['function']."(".saml($v['args']).") kaldt fra ".$v['file'].":".$v['line'];
    return $trace;
    }

function saml($arg)
    {
    if (is_array($arg))
        {
        $s = "[";
        foreach ($arg as $v)
            {
            if ($s != "[") $s .= " , ";
            if      (is_array($v))            $s .= saml($v);
            else if (is_string($v))            $s .= "''".$v."''";
            else if (is_bool($v))            if ($v) $s .= "true"; else $s .= "false";
            else if (is_object($v))            $s .= "{object..}";
            else                            $s .= $v;
            }
        $s .= "]";
        return $s;
        }
    else
        return $arg;
    }

?>
Og den virker sgu!

Eneste bekymring: Jeg opretter (og lukker) en hel connection ved hvert eneste kald...
Avatar billede arne_v Ekspert
29. marts 2011 - 19:24 #15
MySQL er ret hurtigt til at etablere connections, men det koster stadigvaek lidt.

Imidlertid blev pconnect droppet ved skift fra mysql til mysqli, saa mulighederne er begraenset.

Eneste du kan goere er at sende connection link med over i diverse kald.
Avatar billede arne_v Ekspert
29. marts 2011 - 19:25 #16
og et svar fra mig
Avatar billede Slettet bruger
29. marts 2011 - 20:20 #17
Ja, der må noget test til, for at afgøre om det er værd at genbruge connectionen.
Jeg havde dog tænkt mig at erklære den lige over funktionen og så genbruge den med global : )

Ved du (her på falderebet) hvor strafbart det er, ikke at lukke efter sig
- hvis koden skulle stoppe, inden jeg får kaldt $mysqli->close()

Takker for tålmodigheden.
Avatar billede arne_v Ekspert
29. marts 2011 - 23:01 #18
PHP faar lukket naar request er processet og response sendt, saa det er kun et reelt problem ved mange database kald fra noget laengere koerende kode.

Men det er rigtigt daarligt kode.
Avatar billede Slettet bruger
29. marts 2011 - 23:17 #19
Yes! Det var præcis det jeg håbede at høre!
- Tak igen. Skal nok holde mund nu : )
Avatar billede arne_v Ekspert
30. marts 2011 - 05:29 #20
Jeg checkede lige.

Det ser ud til at PDO stadig understoetter persistent connections.
Avatar billede Slettet bruger
30. marts 2011 - 08:28 #21
Det lader til at p:connections er tilbage (i mysqli) fra PHP 5.3:
http://php.net/manual/en/mysqli.persistconns.php

Men ak - min host (Servage.net) er endnu kun på PHP 5.2.42

Min localhost er dog 5.3.3 (Ubuntu) - men jeg kan ikke afgøre om den benytter "Native Driver" - der er ingen forekomst af "mysqlnd" på phpinfo-siden..
Tilgengælg står der:
mysqli.allow_persistent = ON
mysqli.max_persistent = Unlimited

Så jeg forsøgte lige at smække "p:" forrest i URL'en...
- Det havde den absolut ikke noget imod : )
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Computerworld tilbyder specialiserede kurser i database-management

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester