PDO Tutorial For MySQL Developers - Hashphp2
PDO Tutorial For MySQL Developers - Hashphp2
FromHashphp.org
Contents
1WhyusePDO?
2ConnectingtoMySQL
3ErrorHandling
4RunningSimpleSelectStatements
4.1FetchModes
4.2GettingRowCount
4.3GettingtheLastInsertId
5RunningSimpleINSERT,UPDATE,orDELETEstatements
6RunningStatementsWithParameters
6.1NamedPlaceholders
6.2INSERT,DELETE,UPDATEPreparedQueries
6.3PreparingStatementsusingSQLfunctions
6.4Executingpreparedstatementsinaloop
7Transactions
8SeeAlso
9Externallinks
WhyusePDO?
mysql_*functionsaregettingold.Foralongtimenowmysql_*hasbeenatoddswithothercommon
SQLdatabaseprogramminginterfaces.Itdoesn'tsupportmodernSQLdatabaseconceptssuchas
preparedstatements,storedprocs,transactionsetc...andit'smethodforescapingparameterswith
mysql_real_escape_stringandconcatenatingintoSQLstringsiserrorproneandoldfashioned.The
otherissuewithmysql_*isthatithashadalackofattentionlatelyfromdevelopers,itisnotbeing
maintained...whichcouldmeanthingslikesecurityvulnerabilitiesarenotgettingfixed,oritmaystop
workingaltogetherwithnewerversionsofMySQL.AlsolatelythePHPcommunityhaveseenfittostart
asoftdeprecationofmysql_*whichmeansyouwillstartseeingaslowprocessofeventuallyremoving
mysql_*functionsaltogetherfromthelanguage(Don'tworrythiswillprobablybeawhilebeforeit
actuallyhappens!).
PDOhasamuchnicerinterface,youwillendupbeingmoreproductive,andwritesaferandcleaner
code.PDOalsohasdifferentdriversfordifferentSQLdatabasevendorswhichwillallowyoutoeasily
useothervendorswithouthavingtorelearnadifferentinterface.(thoughyouwillhavetolearnslightly
differentSQLprobably).InsteadofconcatenatingescapedstringsintoSQL,inPDOyoubind
parameterswhichisaneasierandcleanerwayofsecuringqueries.Bindingparametersalsoallowfora
performanceincreasewhencallingthesameSQLquerymanytimeswithslightlydifferentparameters.
PDOalsohasmultiplemethodsoferrorhandling.ThebiggestissueIhaveseenwithmysql_*codeis
thatitlacksconsistenthandling,ornohandlingatall!WithPDOinexceptionmode,youcanget
consistenterrorhandlingwhichwillendupsavingyouloadsoftimetrackingdownissues.
PDOisenabledbydefaultinPHPinstallationsnow,howeveryouneedtwoextensionstobeabletouse
PDO:PDO,andadriverforthedatabaseyouwanttouselikepdo_mysql.InstallingtheMySQLdriver
isassimpleasinstallingthephpmysqlpackageinmostdistributions.
ConnectingtoMySQL
oldway:
<?php
$link=mysql_connect('localhost','user','pass');
mysql_select_db('testdb',$link);
mysql_set_charset('UTF8',$link);
newway:allyougottadoiscreateanewPDOobject.PDO'sconstructortakesatmost4parameters,
DSN,username,password,andanarrayofdriveroptions.
ADSNisbasicallyastringofoptionsthattellPDOwhichdrivertouse,andtheconnectiondetails...
YoucanlookupalltheoptionsherePDOMYSQLDSN(http://www.php.net/manual/en/ref.pdo
mysql.connection.php).
<?php
$db=newPDO('mysql:host=localhost;dbname=testdb;charset=utf8mb4','username','password');
Note:Ifyougetanerroraboutcharactersets,makesureyouaddthecharsetparametertotheDSN.
AddingthecharsettotheDSNisveryimportantforsecurityreasons,mostexamplesyou'llseearound
leaveitout.MAKESURETOINCLUDETHECHARSET!
Youcanalsopassinseveraldriveroptionsasanarraytothefourthparameters.Irecommendpassingthe
parameterwhichputsPDOintoexceptionmode,whichIwillexplaininthenextsection.Theother
parameteristoturnoffprepareemulationwhichisenabledinMySQLdriverbydefault,butreally
shouldbeturnedofftousePDOsafelyandisreallyonlyusableifyouareusinganoldversionof
MySQL.
<?php
$db=newPDO('mysql:host=localhost;dbname=testdb;charset=utf8mb4','username','password',array(PDO::ATTR_EMULATE_PRE
PDO::ATTR_ERRMODE
YoucanalsosetsomeattributesafterPDOconstructionwiththesetAttributemethod:
<?php
$db=newPDO('mysql:host=localhost;dbname=testdb;charset=utf8mb4','username','password');
$db>setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
$db>setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
ErrorHandling
Consideryourtypicalmysql_*errorhandling:
<?php
//connectedtomysql
$result=mysql_query("SELECT*FROMtable",$link)ordie(mysql_error($link));
ORdieisaprettybadwaytohandleerrors,yetthisistypicalmysqlcode.Youcan'thandledie()asit
willjustendthescriptabruptlyandthenechotheerrortothescreenwhichyouusuallydoNOTwantto
showtoyourendusersallowingnastyhackersdiscoveryourschema.
PDOhasthreeerrorhandlingmodes.
1.PDO::ERRMODE_SILENTactslikemysql_*whereyoumustcheckeachresultandthenlookat
$db>errorInfo();togettheerrordetails.
2.PDO::ERRMODE_WARNINGthrowsPHPWarnings
3.PDO::ERRMODE_EXCEPTIONthrowsPDOException.Inmyopinionthisisthemodeyou
shoulduse.Itactsverymuchlikeordie(mysql_error());whenitisn'tcaught,butunlikeor
die()thePDOExceptioncanbecaughtandhandledgracefullyifyouchoosetodoso.
<?php
try{
//connectasappropriateasabove
$db>query('hi');//invalidquery!
}catch(PDOException$ex){
echo"AnErroroccured!";//userfriendlymessage
some_logging_function($ex>getMessage());
}
NOTE:youdonothavetohandlewithtrycatchrightaway.Youcancatchitanytimethatis
appropriate.Itmaymakemoresensetocatchitatahigherlevellikeoutsideofthefunctionthatcallsthe
PDOstuff:
<?php
functiongetData($db){
$stmt=$db>query("SELECT*FROMtable");
return$stmt>fetchAll(PDO::FETCH_ASSOC);
}
//thenmuchlater
try{
getData($db);
}catch(PDOException$ex){
//handleme.
}
oryoumaynotwanttohandletheexceptionwithtry/catchatall,andhaveitworkmuchlikeordie()
does.Youcanhidethedangerouserrormessagesinproductionbyturningdisplay_errorsoffandjust
readingyourerrorlog.
RunningSimpleSelectStatements
Considerthemysql_*code:
<?php
$result=mysql_query('SELECT*fromtable')ordie(mysql_error());
$num_rows=mysql_num_rows($result);
while($row=mysql_fetch_assoc($result)){
echo$row['field1'].''.$row['field2'];//etc...
}
InPDOYoucanrunsuchquerieslikethis:
<?php
foreach($db>query('SELECT*FROMtable')as$row){
echo$row['field1'].''.$row['field2'];//etc...
}
query()methodreturnsaPDOStatementobject.Youcanalsofetchresultsthisway:
<?php
$stmt=$db>query('SELECT*FROMtable');
while($row=$stmt>fetch(PDO::FETCH_ASSOC)){
echo$row['field1'].''.$row['field2'];//etc...
}
or
<?php
$stmt=$db>query('SELECT*FROMtable');
$results=$stmt>fetchAll(PDO::FETCH_ASSOC);
//use$results
FetchModes
NotetheuseofPDO::FETCH_ASSOCinthefetch()andfetchAll()codeabove.ThistellsPDOtoreturn
therowsasanassociativearraywiththefieldnamesaskeys.OtherfetchmodeslikePDO::FETCH_NUM
returnstherowasanumericalarray.ThedefaultistofetchwithPDO::FETCH_BOTHwhichduplicates
thedatawithbothnumericalandassociativekeys.It'srecommendedyouspecifyoneortheothersoyou
don'thavearraysthataredoublethesize!PDOcanalsofetchobjectswithPDO::FETCH_OBJ,andcantake
existingclasseswithPDO::FETCH_CLASS.ItcanalsobindintospecificvariableswithPDO::FETCH_BOUND
andusingbindColumnmethod.Thereareevenmorechoices!Readaboutthemallhere:PDOStatement
Fetchdocumentation(http://www.php.net/manual/en/pdostatement.fetch.php).
GettingRowCount
Insteadofusingmysql_num_rowstogetthenumberofreturnedrowsyoucangetaPDOStatementanddo
rowCount()
<?php
$stmt=$db>query('SELECT*FROMtable');
$row_count=$stmt>rowCount();
echo$row_count.'rowsselected';
NOTE:ThoughthedocumentationsaysthismethodisonlyforreturningaffectedrowsfromUPDATE,
INSERT,DELETEqueries,withthePDO_MYSQLdriver(andthisdriveronly)youcangettherowcount
forSELECTqueries.Keepthisinmindwhenwritingcodeformultipledatabases.
ThisisbecauseMySQL'sprotocolisoneoftheveryfewthatgivethisinformationtotheclientfor
SELECTstatements.Mostotherdatabasevendorsdon'tbotherdivulgingthisinformationtotheclientasit
wouldincurmoreoverheadintheirimplementations.
GettingtheLastInsertId
Previouslyinmysql_*youdidsomethinglikethis.
<?php
$result=mysql_query("INSERTINTOtable(firstname,lastname)VALUES('John','Doe')")ordie("InsertFailed".mysql_err
$insert_id=mysql_insert_id();
WithPDOyoujustdorunthelastInsertIdmethod.
<?php
$result=$db>exec("INSERTINTOtable(firstname,lastname)VAULES('John','Doe')");
$insertId=$db>lastInsertId();
RunningSimpleINSERT,UPDATE,orDELETEstatements
Considerthemysql_*code.
<?php
$results=mysql_query("UPDATEtableSETfield='value'")ordie(mysql_error());
$affected_rows=mysql_affected_rows($result);
echo$affected_rows.'wereaffected';
forPDOthiswouldlooklike:
<?php
$affected_rows=$db>exec("UPDATEtableSETfield='value'");
echo$affected_rows.'wereaffected'
DothesameforsimpleDELETE,andINSERTstatementsaswell
RunningStatementsWithParameters
Sofarwe'veonlyshownsimplestatementsthatdon'ttakeinanyvariables.Thesearesimplestatements
andPDOhastheshortcutmethodsqueryforSELECTstatementsandexecforINSERT,UPDATE,DELETE
statements.Forstatementsthattakeinvariableparameters,youshoulduseboundparametermethodsto
executeyourqueriessafely.Considerthefollowingmysql_*code.
<?php
$results=mysql_query(sprintf("SELECT*FROMtableWHEREid='%s'ANDname='%s'",
mysql_real_escape_string($id),mysql_real_escape_string($name)))ordie(mysql_error());
$rows=array();
while($row=mysql_fetch_assoc($results)){
$rows[]=$row;
}
Man!that'sapain,especiallyifyouhavelotsofparameters.ThisishowyoucandoitinPDO:
<?php
$stmt=$db>prepare("SELECT*FROMtableWHEREid=?ANDname=?");
$stmt>execute(array($id,$name));
$rows=$stmt>fetchAll(PDO::FETCH_ASSOC);
Sowhat'sgoingonhere?Thepreparemethodsendsthequerytotheserver,andit'scompiledwiththe'?'
placeholderstobeusedasexpectedarguments.Theexecutemethodsendstheargumentstotheserver
andrunsthecompiledstatement.Sincethequeryandthedynamicparametersaresentseparately,there
isnowaythatanySQLthatisinthoseparameterscanbeexecuted...soNOSQLINJECTIONcan
occur!Thisisamuchbetterandsafersolutionthanconcatenatingstringstogether.
NOTE:whenyoubindparameters,doNOTputquotesaroundtheplaceholders.Itwillcausestrange
SQLsyntaxerrors,andquotesaren'tneededasthetypeoftheparametersaresentduringexecutesothey
arenotneededtobeknownatthetimeofprepare.
There'safewotherwaysyoucanbindparametersaswell.Insteadofpassingthemasanarray,which
bindseachparameterasaStringtype,youcanusebindValueandspecifythetypeforeachparameter:
<?php
$stmt=$db>prepare("SELECT*FROMtableWHEREid=?ANDname=?");
$stmt>bindValue(1,$id,PDO::PARAM_INT);
$stmt>bindValue(2,$name,PDO::PARAM_STR);
$stmt>execute();
$rows=$stmt>fetchAll(PDO::FETCH_ASSOC);
NamedPlaceholders
Nowifyouhavelotsofparameterstobind,doesn'tallthose'?'charactersmakeyoudizzyandarehard
tocount?Well,inPDOyoucanusenamedplaceholdersinsteadofthe'?':
<?php
$stmt=$db>prepare("SELECT*FROMtableWHEREid=:idANDname=:name");
$stmt>bindValue(':id',$id,PDO::PARAM_INT);
$stmt>bindValue(':name',$name,PDO::PARAM_STR);
$stmt>execute();
$rows=$stmt>fetchAll(PDO::FETCH_ASSOC);
Youcanbindusingexecutewithanarrayaswell:
<?php
$stmt=$db>prepare("SELECT*FROMtableWHEREid=:idANDname=:name");
$stmt>execute(array(':name'=>$name,':id'=>$id));
$rows=$stmt>fetchAll(PDO::FETCH_ASSOC);
INSERT,DELETE,UPDATEPreparedQueries
PreparedStatementsforINSERT,UPDATE,andDELETEarenotdifferentthanSELECT.Butletsdosome
examplesanyway:
<?php
$stmt=$db>prepare("INSERTINTOtable(field1,field2,field3,field4,field5)VALUES(:field1,:field2,:field3,:field4,:fie
$stmt>execute(array(':field1'=>$field1,':field2'=>$field2,':field3'=>$field3,':field4'=>$field4,':field5'
$affected_rows=$stmt>rowCount();
<?php
$stmt=$db>prepare("DELETEFROMtableWHEREid=:id");
$stmt>bindValue(':id',$id,PDO::PARAM_STR);
$stmt>execute();
$affected_rows=$stmt>rowCount();
<?php
$stmt=$db>prepare("UPDATEtableSETname=?WHEREid=?");
$stmt>execute(array($name,$id));
$affected_rows=$stmt>rowCount();
PreparingStatementsusingSQLfunctions
YoumayaskhowdoyouuseSQLfunctionswithpreparedstatements.I'veseenpeopletrytobind
functionsintoplaceholderslikeso:
<?php
//THISWILLNOTWORK!
$time='NOW()';
$name='BOB';
$stmt=$db>prepare("INSERTINTOtable(`time`,`name`)VALUES(?,?)");
$stmt>execute(array($time,$name));
Thisdoesnotwork,youneedtoputthefunctioninthequeryasnormal:
<?php
$name='BOB';
$stmt=$db>prepare("INSERTINTOtable(`time`,`name`)VALUES(NOW(),?)");
$stmt>execute(array($name));
YoucanbindargumentsintoSQLfunctionshowever:
<?php
$name='BOB';
$password='badpass';
$stmt=$db>prepare("INSERTINTOtable(`hexvalue`,`password`)VALUES(HEX(?),PASSWORD(?))");
$stmt>execute(array($name,$password));
AlsonotethatthisdoesNOTworkforLIKEstatements:
<?php
//THISDOESNOTWORK
$stmt=$db>prepare("SELECTfieldFROMtableWHEREfieldLIKE%?%");
$stmt>bindParam(1,$search,PDO::PARAM_STR);
$stmt>execute();
Sodothisinstead:
<?php
$stmt=$db>prepare("SELECTfieldFROMtableWHEREfieldLIKE?");
$stmt>bindValue(1,"%$search%",PDO::PARAM_STR);
$stmt>execute();
NoteweusedbindValueandnotbindParam.Tryingtobindaparameterbyreferencewillgeneratea
FatalErrorandthiscannotbecaughtbyPDOExceptioneither.
Executingpreparedstatementsinaloop
Preparedstatementsexcelinbeingcalledmultipletimesinarowwithdifferentvalues.Becausethesql
statementgetscompiledfirst,itcanbecalledmultipletimesinarowwithdifferentarguments,and
you'llgetabigspeedincreasevscallingmysql_queryoverandoveragain!
TypicallythisisdonebybindingparameterswithbindParam.bindParamismuchlikebindValueexcept
insteadofbindingthevalueofavariable,itbindsthevariableitself,sothatifthevariablechanges,it
willbereadatthetimeofexecute.
<?php
$values=array('bob','alice','lisa','john');
$name='';
$stmt=$db>prepare("INSERTINTOtable(`name`)VALUES(:name)");
$stmt>bindParam(':name',$name,PDO::PARAM_STR);
foreach($valuesas$name){
$stmt>execute();
}
Transactions
Here'sanexampleofusingtransactionsinPDO:(notethatcallingbeginTransaction()turnsoffauto
commitautomatically):
<?php
try{
$db>beginTransaction();
$db>exec("SOMEQUERY");
$stmt=$db>prepare("SOMEOTHERQUERY?");
$stmt>execute(array($value));
$stmt=$db>prepare("YETANOTHERQUERY??");
$stmt>execute(array($value2,$value3));
$db>commit();
}catch(PDOException$ex){
//Somethingwentwrongrollback!
$db>rollBack();
echo$ex>getMessage();
}
SeeAlso
ValidationandSQLInjection
Externallinks
PDODocumentation(http://www.php.net/manual/en/book.pdo.php)
Retrievedfrom"http://wiki.hashphp.org/index.php?
title=PDO_Tutorial_for_MySQL_Developers&oldid=641"
Thispagewaslastmodifiedon17February2016,at09:33.