1. PHP 101: PDO
or:
How I Learned to Stop Worrying and Love
Data Access Abstraction
Jeremy Kendall | Memphis PHP | July 26, 2012
2. What is PDO?
• PDO == PHP Data Objects
• Lightweight, consistent interface for
accessing databases in PHP
• Provides data access abstraction, not
database abstraction
• Ships with PHP 5.1
• PECL extension for 5.0
3. Drivers include . . .
• MySQL
• PostgreSQL
• SQL Server
• SQLite
• and more . . .
4. Which drivers do I have?
• var_dump(PDO::getAvailableDrivers());
• If you don’t see what you need, check php.ini
5. Working with databases
• Solved problem, you don’t have to write
anything yourself
• PHP and MySQL have an especially rich
history
• CRUD is the easy part
• Creating clean, maintainable code is the
challenge
15. mysqli
<?php
$conn = mysqli_connect('localhost', 'testuser', 'testpass', 'bookshelf')
or die('Could not connect: ' . mysqli_connect_error());
$result = mysqli_query($conn, 'SELECT * FROM bookshelf ORDER BY title')
or die('Invalid query: ' . mysqli_error($conn));
?>
<table>
<tr>
<th>Title</th><th>Author</th>
</tr>
<?php
while ($row = mysqli_fetch_assoc($result)) {
echo "<tr>";
echo "<td>{$row['title']}</td><td>{$row['author']}</td>";
echo "</tr>";
}
?>
</table>
<?php
mysqli_close($conn);
16. Refactoring problems
// You can't simply find and replace
// because of differences in the interfaces
// mysql_query wants query first and connection second
$result = mysql_query($query, $conn);
// mysqli_query is exactly opposite
$result = mysqli_query($conn, $query);
17. Refactoring problems
// You can't simply find and replace
// because of differences in the interfaces
// mysql_query wants query first and connection second
$result = mysql_query($query, $conn);
// mysqli_query is exactly opposite
$result = mysqli_query($conn, $query);
18. Refactoring problems
// You can't simply find and replace
// because of differences in the interfaces
// mysql_query wants query first and connection second
$result = mysql_query($query, $conn);
// mysqli_query is exactly opposite
$result = mysqli_query($conn, $query);
27. You could roll your own
<?php
class MyDb
{
public function __construct($host, $username, $password, $dbname)
{
}
public function query($sql, array $bind)
{
}
// . . .
}
38. Connecting
• DSN required, username & pass optional
• A connection error always throws an
Exception, so use try/catch
• Closing a connection is as simple as $dbh = null
39. Errors and Exceptions
• Three error modes
• PDO::ERRMODE_SILENT
• PDO::ERRMODE_WARNING
• PDO::ERRMODE_EXCEPTION
• Always use exceptions
• No, really, use exceptions
41. Delete example
/* quote string */
$title = $dbh->quote('Refactoring');
/* Delete a book */
$count = $dbh->exec("DELETE FROM bookshelf WHERE title = $title");
/* Return number of rows that were deleted */
print("Deleted $count rows.n");
42. Delete example
/* quote string */
$title = $dbh->quote('Refactoring');
/* Delete a book */
$count = $dbh->exec("DELETE FROM bookshelf WHERE title = $title");
/* Return number of rows that were deleted */
print("Deleted $count rows.n");
43. Delete example
/* quote string */
$title = $dbh->quote('Refactoring');
/* Delete a book */
$count = $dbh->exec("DELETE FROM bookshelf WHERE title = $title");
/* Return number of rows that were deleted */
print("Deleted $count rows.n");
44. Delete example
/* quote string */
$title = $dbh->quote('Refactoring');
/* Delete a book */
$count = $dbh->exec("DELETE FROM bookshelf WHERE title = $title");
/* Return number of rows that were deleted */
print("Deleted $count rows.n");
45. In almost all cases, favor
prepared statements
over PDO::exec()
46. Prepared statements
• To know them is to love them
• No more SQL injection
• Can execute same statement multiple times
with different values
• Efficient (in most cases)
• Positional, named
• The one feature PDO emulates
47. Prepared statements
Using named parameters:
$sql = "INSERT INTO bookshelf (title, author) VALUES (:title, :author)";
$statement = $dbh->prepare($sql);
$statement->bindParam('title', $title);
$statement->bindParam('author', $author);
$books = array(
"Clean Code" => "Robert C. Martin",
"Refactoring" => "Martin Fowler",
"Test-Driven Development" => "Kent Beck",
"The Agile Samurai" => "Jonathan Rasmusson",
"Working Effectively with Legacy Code" => "Michael Feathers"
);
foreach ($books as $title => $author) {
$setup->execute();
}
48. Prepared statements
Using named parameters:
$sql = "INSERT INTO bookshelf (title, author) VALUES (:title, :author)";
$statement = $dbh->prepare($sql);
$statement->bindParam('title', $title);
$statement->bindParam('author', $author);
$books = array(
"Clean Code" => "Robert C. Martin",
"Refactoring" => "Martin Fowler",
"Test-Driven Development" => "Kent Beck",
"The Agile Samurai" => "Jonathan Rasmusson",
"Working Effectively with Legacy Code" => "Michael Feathers"
);
foreach ($books as $title => $author) {
$setup->execute();
}
49. Prepared statements
Using named parameters:
$sql = "INSERT INTO bookshelf (title, author) VALUES (:title, :author)";
$statement = $dbh->prepare($sql);
$statement->bindParam('title', $title);
$statement->bindParam('author', $author);
$books = array(
"Clean Code" => "Robert C. Martin",
"Refactoring" => "Martin Fowler",
"Test-Driven Development" => "Kent Beck",
"The Agile Samurai" => "Jonathan Rasmusson",
"Working Effectively with Legacy Code" => "Michael Feathers"
);
foreach ($books as $title => $author) {
$setup->execute();
}
50. Prepared statements
Using named parameters:
$sql = "INSERT INTO bookshelf (title, author) VALUES (:title, :author)";
$statement = $dbh->prepare($sql);
$statement->bindParam('title', $title);
$statement->bindParam('author', $author);
$books = array(
"Clean Code" => "Robert C. Martin",
"Refactoring" => "Martin Fowler",
"Test-Driven Development" => "Kent Beck",
"The Agile Samurai" => "Jonathan Rasmusson",
"Working Effectively with Legacy Code" => "Michael Feathers"
);
foreach ($books as $title => $author) {
$setup->execute();
}
52. Prepared statements
Array of named params to PDO::execute
$sql = "INSERT INTO bookshelf (title, author) VALUES (:title, :author)";
$statement = $dbh->prepare($sql);
$statement->execute(array('title' => $title, 'author' => $author));
53. Prepared statements
Array of named params to PDO::execute
$sql = "INSERT INTO bookshelf (title, author) VALUES (:title, :author)";
$statement = $dbh->prepare($sql);
$statement->execute(array('title' => $title, 'author' => $author));
54. Prepared statements
Array of named params to PDO::execute
$sql = "INSERT INTO bookshelf (title, author) VALUES (:title, :author)";
$statement = $dbh->prepare($sql);
$statement->execute(array('title' => $title, 'author' => $author));
55. Select
• Gets data from the database
• Results returned as PDOStatement
• How data is fetched from statement
depends on fetch mode
• Grab all results at once with fetchAll()
56. Fetch modes
• Can be set in constructor or later with
PDO::setAttribute() or
PDOStatement::setFetchMode()
• PDO::FETCH_ASSOC
• PDO::FETCH_BOTH (default)
• PDO::FETCH_NUM
• PDO::FETCH_OBJ
• to name a few . . .
57. Fetching
// Gets all books in database
$stmt = $dbh->query("SELECT * FROM bookshelf ORDER BY title");
// Returns array of all results
$books = $stmt->fetchAll();
// Returns next row in manner determined by fetch mode
$row = $stmt->fetch();
// Can choose fetch mode at time of fetch
$row = $stmt->fetch(PDO::FETCH_OBJ);
58. Fetching
// Gets all books in database
$stmt = $dbh->query("SELECT * FROM bookshelf ORDER BY title");
// Returns array of all results
$books = $stmt->fetchAll();
// Returns next row in manner determined by fetch mode
$row = $stmt->fetch();
// Can choose fetch mode at time of fetch
$row = $stmt->fetch(PDO::FETCH_OBJ);
59. Fetching
// Gets all books in database
$stmt = $dbh->query("SELECT * FROM bookshelf ORDER BY title");
// Returns array of all results
$books = $stmt->fetchAll();
// Returns next row in manner determined by fetch mode
$row = $stmt->fetch();
// Can choose fetch mode at time of fetch
$row = $stmt->fetch(PDO::FETCH_OBJ);
60. Fetching
// Gets all books in database
$stmt = $dbh->query("SELECT * FROM bookshelf ORDER BY title");
// Returns array of all results
$books = $stmt->fetchAll();
// Returns next row in manner determined by fetch mode
$row = $stmt->fetch();
// Can choose fetch mode at time of fetch
$row = $stmt->fetch(PDO::FETCH_OBJ);
61. Fetching
// Gets all books in database
$stmt = $dbh->query("SELECT * FROM bookshelf ORDER BY title");
// Returns array of all results
$books = $stmt->fetchAll();
// Returns next row in manner determined by fetch mode
$row = $stmt->fetch();
// Can choose fetch mode at time of fetch
$row = $stmt->fetch(PDO::FETCH_OBJ);
62. Fetching
PDOStatement implements Traversable,
which allows you to:
$stmt = $dbh->query('SELECT * FROM bookshelf ORDER BY title');
?>
<table>
<tr>
<th>Title</th><th>Author</th>
</tr>
<?php
foreach ($stmt as $row) {
echo "<tr>";
echo "<td>{$row['title']}</td><td>{$row['author']}</td>";
echo "</tr>";
}
?>
</table>
63. Fetching
PDOStatement implements Traversable,
which allows you to:
$stmt = $dbh->query('SELECT * FROM bookshelf ORDER BY title');
?>
<table>
<tr>
<th>Title</th><th>Author</th>
</tr>
<?php
foreach ($stmt as $row) {
echo "<tr>";
echo "<td>{$row['title']}</td><td>{$row['author']}</td>";
echo "</tr>";
}
?>
</table>
64. Fetching
PDO::FETCH_BOUND is slick
$stmt = $dbh->query('SELECT id, title, author FROM bookshelf ORDER BY title');
$stmt->bindColumn('title', $title);
$stmt->bindColumn('author', $author);
?>
<table>
<tr>
<th>Title</th><th>Author</th>
</tr>
<?php
while ($stmt->fetch(PDO::FETCH_BOUND)) {
echo "<tr>";
echo "<td>$title</td><td>$author</td>";
echo "</tr>";
}
?>
</table>
65. Fetching
PDO::FETCH_BOUND is slick
$stmt = $dbh->query('SELECT id, title, author FROM bookshelf ORDER BY title');
$stmt->bindColumn('title', $title);
$stmt->bindColumn('author', $author);
?>
<table>
<tr>
<th>Title</th><th>Author</th>
</tr>
<?php
while ($stmt->fetch(PDO::FETCH_BOUND)) {
echo "<tr>";
echo "<td>$title</td><td>$author</td>";
echo "</tr>";
}
?>
</table>
66. Fetching
PDO::FETCH_BOUND is slick
$stmt = $dbh->query('SELECT id, title, author FROM bookshelf ORDER BY title');
$stmt->bindColumn('title', $title);
$stmt->bindColumn('author', $author);
?>
<table>
<tr>
<th>Title</th><th>Author</th>
</tr>
<?php
while ($stmt->fetch(PDO::FETCH_BOUND)) {
echo "<tr>";
echo "<td>$title</td><td>$author</td>";
echo "</tr>";
}
?>
</table>
68. PDO::lastInsertId()
• Returns the ID of the last inserted row
• Or the last value from a sequence object
• Be careful, some databases don’t support
this and may exhibit unexpected behavior
71. Drawbacks
• Not all vendor specific features supported
• Interface is almost the same for all
databases
• Slower in some cases
• Mediocre Oracle support (who uses
Oracle?)
72. Portable, mostly
• Every DB has its own oddities and gotchas
• PDO offers data access abstraction
• You’ll likely still have to tweak if you’re doing
anything other than very simple CRUD
• Still better than mysql_*, mysqli_*, sqlsrv_*
• Choose a DBAL like Doctrine if you need
more
73. Credits
• PHP Data Objects by Wez Furlong: http://
www.slideshare.net/wezfurlong/php-data-objects
• Nettuts+: http://net.tutsplus.com/tutorials/php/why-you-
should-be-using-phps-pdo-for-database-access/
• Introduction to PDO: http://www.dreamincode.net/
forums/topic/214733-introduction-to-pdo/
• php.net: http://us.php.net/manual/en/class.pdo.php