MySQLi for begyndere

I denne artikel berører jeg MySQLi som aflæseren til MySQL udvidelsen. Dette for at gøre det nemmere for dig at se hvad du vinder og hvordan du overgår til et bedre database API.

Artiklen vil ikke berøre PDO.

  1. Hvad er MySQLi
  2. Hvad er forskellen?
    1. Prepared statements?
    2. Det er let at forbinde til databasen
    3. Du skal ikke escape dit input
    4. Du skal selv eksekvere din forespørgsel
  3. Hvordan bruger jeg MySQLi?
    1. Bundne parametre?
    2. Type hinting?
  4. Men det virker så besværligt?
    1. Prepared statements er sikkert
    2. Mere funktionalitet
    3. Det er objekt orienteret
    4. Det er hurtigere
  5. Mangler artiklen uddybning?

Hvad er MySQLi

MySQLi står for MySQL Improved Extension og er afløseren for det gamle MySQL.

Fra PHP 5.5.0 vil MySQL være deprecated, hvilket vil sige at man begynder at få notices og warnings, når man bruger dem. På et senere tidspunkt vil de slet ikke være med i PHP overhovedet.

Hvad er forskellen?

Forskellene kan måske virke små, men der er en overvejende positiv ting ved MySQLi - udvidelsen tilbyder dig en objekt orienteret tilgang til databasen.

Udover det, så kan MySQLi også anvende nyere funktionalitet som MySQL databasen tilbyder, herunder også noget man kalder for "prepared statements".

Prepared statements?

Prepared statements er en måde at eksekvere dine forespørgsler til databasen på en effektiv måde, og giver dig også lidt ekstra sikkerhed i form af den måde som dataene sendes til databasen.

For at illustrere, vil jeg give to eksempler på den samme forespørgsel. Den ene med den velkendte MySQL - den anden med det nye og smarte MySQLi.

MySQL udgaven

<?php

if ( ! $link mysql_connect("localhost""brugernavn""kodeord") )
{
    echo 
mysql_error();
    exit;
}

if( ! 
mysql_select_db('min_database'$link) )
{
    echo 
mysql_error();
    exit;        
}

$username mysql_real_escape_string($_POST['username']);
$password mysql_real_escape_string($_POST['password']);

$query mysql_query("SELECT * FROM users WHERE username = '".$username."' AND password = '".$password."' LIMIT 1");
if( 
mysql_num_rows($query) > )
{
    
$user mysql_fetch_array($result);
}
else
{
        echo 
"Der blev ikke fundet nogen bruger med de oplysninger";
}

MySQLi udgaven

<?php

$mysqli 
= new mysqli("localhost""brugernavn""kodeord""min_database");

if( 
$mysqli->connect_error )
{
    echo 
$mysqli->connect_error;
    exit;
}

if( 
$stmt $mysqli->prepare("SELECT * FROM users WHERE username = ? AND password = ? LIMIT 1") )
{
    
$stmt->bind_param("ss"$_POST['username'], $_POST['password']);
    
$stmt->execute();
    
    if( 
$stmt->num_rows )
    {
        
$result $stmt->get_result();
        
$user $result->fetch_assoc();
    }
    else
    {
        echo 
"Der blev ikke fundet nogen bruger med de oplysninger";
    }
}

Der er nogle meget fundamentelle forskelle man bør lægge mærke til.

Det er let at forbinde til databasen

En ting som jeg personligt mener er rigtig lækkert ved MySQLi, er måden at forbinde til databasen på. Det kan klares på en enkelt linie. I modsætning til den gamle MySQL udvidelse, hvor du både bruger mysql_connect() og mysql_select_db() - og samtidig skal man kontrollere at begge funktioner blev udført uden fejl.

Med MySQLi forbinder du til databasen på en linie, og skal egentlig kun kontrollere den forbindelse for at vide om du er klar til at bruge MySQL databasen.

Nu ved jeg godt at der sidder et par erfarne udviklere og vil selvfølgelig sige at mysql_select_db() ikke er nødvendigt - men jeg vil mene for nemhedens skyld er det væsentligt rarere at arbejde med at det er unødvendigt at angive database-navnet i hver database-forespørgsel.

Du skal ikke escape dit input

For at sikre dig mod skadelig kode bliver gemt i din database, vil du sikkert være vant til at escape dit indhold med mysql_real_escape_string() med den gamle MySQL udvidelse. Du vil gennem længere tid være blevet forsikret om at dette er et minimumskrav, når man snakker om databaseindsættelse.

Med MySQLi vil du dog kunne springe over den del, netop fordi du bruger prepared statements. Når du bruger prepared statements, vil bundne paramatre (det kommer vi til) blive escapede automatisk. Dette gøres ved at du hinter typen af den bundne variabler (det kommer vi også til) - derved klarer databasen den korrekte konvertering af inputtet.

Du skal selv eksekvere din forespørgsel

Selvom det måske virker åbenlyst at man skal sørge for at forespørgslen bliver sendt til serveren, så skal man ikke tage det for givet.

Idet du bruger mysql_query() eksekverer du automatisk din forespørsel med den gamle MySQL udvidelse. Med den nye udvidelse, MySQLi, vil du også kunne gøre det ved hjælp af mysqli::query(). Herunder illustreres hvor ens de to udvidelser kan virke:

<?php
// MySQLi
$mysqli = new mysqli("localhost""brugernavn""kodeord""min_database");

if( 
$mysqli->connect_error )
{
    echo 
$mysqli->connect_error;
    exit;
}

if( 
$result $mysql->query("SELECT * FROM users") )
{
    while( 
$row $result->row() )
    {
        echo 
$row->username;
    }
}

//MySQL
if ( ! $link mysql_connect("localhost""brugernavn""kodeord") )
{
    echo 
mysql_error();
    exit;
}

if( ! 
mysql_select_db('min_database'$link) )
{
    echo 
mysql_error();
    exit;        
}

if( 
$query mysql_query("SELECT * FROM users") )
{
    while( 
$row mysql_fetch_object($query) )
    {
        echo 
$row->username;
    }
}

Hvis man i forvejen synes at OOP er meget intuitivt og pænt at se på, vil MySQLi udvidelsen også straks falde i ens smag.

Hvordan bruger jeg MySQLi?

For at komme et par tidligere brugte ord i møde, må vi hellere få kigget lidt på hvad de egentlig indebærer.

Bundne parametre?

For at medsende input sammen med din forespørgsel, skal du angive hvor og hvor meget input du vil medsende - ligesom du plejer. Men når du bruger prepared statements, gøres det ved at bruge et spørgsmålstegn. Ingen quotes, ingen escaping - bare et spørgsmålstegn:

<?php

$stmt 
$mysqli->prepare("SELECT * FROM news WHERE category = ? AND author_id = ?");

Ved at analysere ovenstående stump kan vi konkludere at vi forsøger at lave en prepared statement med to forskellige input. Værdien af disse repræsenteres lige nu af anonyme variabler ved hjælp spørgsmålstegnene. For at medsende de variabler som vi skal bruge i eksemplet, $category og $author_id.

Dette gøres ved at binde parametre til vores prepared statement, som vi har tilknyttet variablen $stmt.

<?php

$category 
'hot';
$author_id 7;

if( 
$stmt $mysqli->prepare("SELECT * FROM news WHERE category = ? AND author_id = ?") )
{
    
$stmt->bind_param('si'$category$author_id);
}

Vi har nu fået bundet bundet vores parametre til vores prepared statement. Det betyder at værdien i vores variabler, nu er sat ind de rigtige steder.

Det der er rigtig vigtigt her er at når du binder parametre til dit prepared statement, skal de bindes i samme rækkefølge som dine spørgsmålstegn er skrevet i.

Nu tænker du måske "meget fint, men hvad pokker er det der 'si' du skrev?" - og det er hvad man kalder for type hinting.

Type hinting?

Når du type hinter, fortæller du hvad det er du forventer den data du netop vil bruge er for en type. Til MySQLi findes der 4 typer man kan hinte til:

  • i - hvis din variabel er et heltal (integer)
  • d - hvis din variabel er et decimaltal (double)
  • s - hvis din variabel er en string (string)
  • b - hvis din variabel er en blob/binær data (blob i packets)

Det betyder at, jævnfør eksemplet fra før, så svarer 'si' til at vi sender en streng og et heltal. Der er ingen adskiller mellem hintene som eksempelvis kommaer, pipes eller lignende. Men det er vigtigt at dine hints kommer i samme rækkefølge som dine parametre. Ellers vil dit input blive konverteret til den type som du hinter til. Herunder ses to eksempler - først et korrekt og derefter et der ikke er korrekt - som illustrerer forskellen på, at tingene står i den rigtige rækkefølge.

<?php

// Korrekt hint og parameter binding.
$category 'hot';
$author_id 7;

if( 
$stmt $mysqli->prepare("SELECT * FROM news WHERE category = ? AND author_id = ?") )
{
    
$stmt->bind_param('si'$category$author_id);
}
//SELECT * FROM news WHERE category = 'hot' AND author_id = 7

// Ukorrekt hint og parameter binding.
$category 'hot';
$author_id 7;

if( 
$stmt $mysqli->prepare("SELECT * FROM news WHERE category = ? AND author_id = ?") )
{
    
$stmt->bind_param('is'$category$author_id);
}
//SELECT * FROM news WHERE category = '0' AND author_id = '7'

Men det virker så besværligt?

Indrømmet, til at starte med fandt jeg det faktisk også ret besværligt at sætte sig ind i. Men det har så mange fordele at gøre det.

Prepared statements er sikkert

Det giver dig en mere sikker og let måde at kommunikere med databasen på. Og hvis der er noget, du som udvikler bør fokusere på, så er det sikkerhed.

Mere funktionalitet

MySQL har siden den første PHP udvidelse fået langt flere funktioner, som den gamle udvidelse ikke kan understøtte. Dem får du adgang til med MySQLi!

Det er objekt orienteret

Alle udviklere stræber efter at gøre sin kode så effektiv og så genbrugbar som overhovedet muligt. Det giver MySQLi dig let mulighed for.

Det er hurtigere

MySQLi er hurtigere - der kommunikeres binært ved prepared statements i stedet for serialized strenge. Og det giver lidt hastighedsoptimering i kommunikationen.

Og der er flere fordele! Du skal også bare selv kunne finde dem.

Du kan læse meget mere om MySQLi på php.net, hvor der også er gode eksempler på lidt mere avanceret brug.

Mangler artiklen uddybning?

Kontakt mig hvis du synes noget i artiklen mangler, ikke stemmer eller bare ikke er specifik nok om et bestemt punkt.