mysql
— Classic extension, procedural (= based on methods)mysqli
— Improved version of the mysql
extension. Supports Prepared Statements, Multiple Statements, Transactions, etc. Both OO and proceduralpdo_mysql
— OO based; Implements PHP Data Objects, an interface to communicating with a database server.mysql
and mysqli
extensions are outdated
mysql
is procedural. mysqli
's OO form not robust as it clearly was an afterthoughtmysql
and mysqli
are MySQL only. PDO plays nice with multiple DBMS's such as MSSQL, PostgreSQL, etc.php.ini
;
to activatephpinfo()
to check if PDO and pdo_mysql
are activated
config.php
to store our credentials in
<?php
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', 'Azerty123');
define('DB_NAME_FF', 'fotofactory');
define('DB_NAME_STATUS', 'status');
//EOF
/assets/07/examples
)PDO
with params:
<?php
require_once 'config.php';
$db = new PDO('mysql:host=' . DB_HOST .';dbname=' . DB_NAME_FF . ';charset=utf8mb4', DB_USER, DB_PASS);
echo 'Connected to the database';
//EOF
<?php
// Include config
require_once 'config.php';
// Make Connection
$db = new PDO('mysql:host=' . DB_HOST .';dbname=does_not_exist;charset=utf8mb4', DB_USER, DB_PASS);
echo 'Connected to the database';
// ... your query magic here
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42000] [1049] Unknown database 'does_not_exist'' in /Users/bramus/Dropbox/Kaho/.../assets/07/examples/02.php:7 Stack trace: #0 /Users/bramus/Dropbox/Kaho/.../assets/07/examples/02.php(7): PDO->__construct('mysql:host=loca...', 'root', 'Azerty123') #1 {main} thrown in /Users/bramus/Dropbox/Kaho/Lesactiviteiten/WS1 - Serverside/2013-2014/slidedeck/assets/07/examples/02.php on line 7
try/catch
structtry {
$db = new PDO('mysql:host=' . DB_HOST .';dbname=does_not_exist;charset=utf8mb4', DB_USER, DB_PASS);
} catch (Exception $e) {
exit('Could not connect to database server or access database');
}
<?php
function showDbError($type) {
header('location: error.php?type=db&detail=' . $type);
exit();
}
require_once 'config.php';
try {
$db = new PDO('mysql:host=' . DB_HOST .';dbname=does_not_exist;charset=utf8mb4', DB_USER, DB_PASS);
} catch (Exception $e) {
showDbError('connect');
}
//EOF
showDbError()
and log it in a file/db<?php
function showDbError($type, $msg) {
file_put_contents(
__DIR__ . '/error_log_mysql',
PHP_EOL . (new DateTime())->format('Y-m-d H:i:s') . ' : ' . $msg,
FILE_APPEND
);
header('location: error.php?type=db&detail=' . $type);
exit();
}
try {
$db = new PDO('mysql:host=' . DB_HOST .';dbname=does_not_exist;charset=utf8mb4', DB_USER, DB_PASS);
} catch (Exception $e) {
showDbError('connect', $e->getMessage());
}
SELECT
, SHOW
, DESCRIBE
, EXPLAIN
, …)INSERT
, UPDATE
, DELETE
, DROP
, …)PDO::query()
⚑ for queries that yield a resultset$stmt = $db->query('SELECT * FROM collections WHERE user_id = 2');
var_dump($stmt);
PDO::exec()
⚑ for queries that yield no resultset$stmt = $db->exec('DELETE FROM collections WHERE user_id = 10');
var_dump($stmt);
PDO::setAttribute()
⚑ to change the error mode so that exceptions are thrown when a faulty query is executed
try {
$db = new PDO('mysql:host=' . DB_HOST .';dbname=' . DB_NAME_FF . ';charset=utf8mb4', DB_USER, DB_PASS);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (Exception $e) {
showDbError('connect', $e->getMessage());
}
$stmt = $db->exec('DELETE FROM inexistent_table WHERE user_id = 10');
detail.php?id=X
<?php
…
// Get ID from URL
$id = isset($_GET['id']) ? $_GET['id'] : 0;
// Get collection from database
$stmt = $db->query('SELECT * FROM collections WHERE id = ' . $id);
// Handle result
…
detail.php?id=test
the query will become broken: SELECT * FROM collections WHERE id = test
is an invalid querySELECT * FROM products WHERE id = "test"
is a valid query$stmt = $db->query('SELECT * FROM collections WHERE user_id = "' . $id . '"');
detail.php?id=te"st
the query will become broken: SELECT * FROM products WHERE id = "te"st"
is an invalid queryaddslashes()
SELECT * FROM products WHERE id = "te\"st"
is a valid query$stmt = $db->query('SELECT * FROM collections WHERE user_id = "' . addslashes($id) . '"');
$id
to an integer
$stmt = $db->query('SELECT * FROM collections WHERE id = ' . (int) $id);
addslashes()
? Alas, no dice:
addslashes()
does not recognize the multibyte char 0xbf27
(뼧) and treats it as two single bytes: 0xBF
(¿) and 0x27
(') → PHP will escape that 0x27
with a backslash (0x5c
) and we end up with the char sequence 0xbf5c27
0xbf5c
(뽜) as a multibyte char, leaving an unescaped 0x27
in the query (!) → Boom, you've just injected a single quote.
PDO::quote()
⚑ to escape a parameter properly
$stmt = $db->query('SELECT * FROM collections WHERE id = ' . $db->quote($userId, PDO::PARAM_INT));
$db = new PDO('mysql:host=' . DB_HOST .';dbname=' . DB_NAME_FF . ';charset=utf8mb4', DB_USER, DB_PASS);
PDO::quote()
will add quotes around the value where needed
$stmt = $db->query('SELECT * FROM collections WHERE name = ' . $db->quote($name, PDO::PARAM_STR));
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// Make Connection
try {
$db = new PDO('mysql:host=' . DB_HOST .';dbname=' . DB_NAME . ';charset=utf8mb4', DB_USER, DB_PASS);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch (Exception $e) {
showDbError('connect', $e->getMessage());
}
PDOStatement::bindValue()
⚑
$stmt = $db->prepare('SELECT * FROM collections WHERE id = ?');
$stmt->bindValue(1, $id, PDO::PARAM_INT);
$stmt->execute();
$stmt = $db->prepare('SELECT * FROM collections WHERE id = :id');
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$stmt = $db->prepare('SELECT * FROM collections WHERE id = ?');
$stmt->execute(array($id));
PDOStatement::execute()
⚑
$stmt = $db->prepare('INSERT INTO collections (name, user_id) VALUES (?, ?)');
$stmt->execute(array('bramus-test', 4));
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
PDOStatement::fetchAll()
⚑ to fetch the entire resultset into an array
$stmt =$db->prepare('SELECT * FROM collections WHERE user_id = ? OR name = ?');
$stmt->execute(array(2, 'russia'));
$collections = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo '<pre>';
var_dump($collections);
echo '</pre>';
$collections = $stmt->fetchAll(PDO::FETCH_OBJ);
PDOStatement::fetch()
⚑ to fetch the returned rows one bye one
$stmt =$db->prepare('SELECT * FROM collections WHERE user_id = ? OR name = ?');
$stmt->execute(array(2, 'russia'));
while ($collection = $stmt->fetch(PDO::FETCH_ASSOC)) {
var_dump($collection);
}
$stmt = $db->prepare('SELECT * FROM collections WHERE id = ?');
$stmt->execute(array(1));
$collection = $stmt->fetch(PDO::FETCH_ASSOC);
var_dump($collection);
PDOStatement::fetchColumn()
⚑ to fetch the value of a column from the first row
$stmt = $db->prepare('SELECT name, user_id FROM collections WHERE id = ?');
$stmt->execute(array(1));
var_dump($stmt->fetchColumn());
$stmt = $db->prepare('SELECT name, user_id FROM collections WHERE id = ?');
$stmt->execute(array(1));
var_dump($stmt->fetchColumn(1));
PDO::lastInsertId()
⚑ to know the last inserted id (AUTO_INCREMENT
) after executing an INSERT
query
$stmt = $db->prepare('INSERT INTO collections(name, user_id) VALUES (?,?)');
$stmt->execute(array('Trip to Amsterdam', 10));
echo 'Created album with ID ' . $db->lastInsertId();
PDOStatement::rowCount()
⚑ to know the number of affected rows made by UPDATE
and DELETE
queries
$stmt = $db->prepare('DELETE FROM collections WHERE user_id = ?');
$stmt->execute(array(10));
echo 'Deleted ' . $stmt->rowCount() . ' rows';
UTF-8
, everywhere.
utf8mb4
/utf8mb4_unicode_ci
CREATE DATABASE `mydb`
DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `records` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
$db = new PDO('mysql:host=' . DB_HOST .';dbname=' . DB_NAME_FF . ';charset=utf8mb4', DB_USER, DB_PASS);
<meta charset="utf-8" />
utf8
) was incomplete: it didn't support astral symbols (= emoji), which require 4 byte encodingutf8mb4
charsetutf8mb4
NOW()
date_format()
SELECT date_format("%Y-%m-%d", publishTimestamp) AS readabledate FROM ...
%
unix_timestamp()
SELECT unix_timestamp(publishDateTime) AS timestamp FROM ...
datetime
as the field type
$data['publish_date'] = (new Datetime())->format('Y-m-d H:i:s');
$month = (new Datetime($dbResult['publish_date']))->format('m')
$stmt = $db->prepare('SELECT * FROM blogposts WHERE publish_date ?');
$stmt->execute(array((new DateTime())->format('Y-m-d H:i:s')));
$id = (int) isset($_GET['id']) ? $_GET['id'] : 0;
$stmt = $db->prepare('SELECT id, title, content FROM blogs WHERE id = ?');
$stmt->execute(array($id));
$blogpost = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $db->prepare('SELECT id, blog_id, comment, publishdate, author_id FROM comments WHERE blog_id = ?');
$stmt->execute(array($id));
$comments = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($comments as $c) {
$stmt = $db->prepare('SELECT * FROM authors WHERE id = ?');
$stmt->execute(array($c['author_id']));
$author = $stmt->fetch(PDO::FETCH_ASSOC);
// … show comment + author info
}
$id = (int) isset($_GET['id']) ? $_GET['id'] : 0;
$stmt = $db->prepare('SELECT id, title, content FROM blogs WHERE id = ?');
$stmt->execute(array($id));
$blogpost = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $db->prepare('SELECT c.id, c.blog_id, c.comment, c.publishdate, c.author_id, a.name, a.email, a.url FROM comments AS c INNER JOIN authors AS a ON c.author_id = a.id WHERE c.blog_id = ?');
$stmt->execute(array($id));
$comments = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($comments as $c) {
// … show comment + author info
}
md5()
⚑ and sha1()
⚑ are outdated/too weak, don't use them
crypt()
⚑ strong but no long preferredpassword_hash()
⚑ and password_verify()
⚑
echo password_hash('bramus', PASSWORD_DEFAULT); // store this into your DB
$pass = isset($_POST['password']) ? $_POST['password'] : '';
// Value as stored in database
$encrypted = '$2y$10$Ae/ZoEnFze9SZKcgbNBluOObABeejekNoJ2iNmyf4QM5IYSIyzFPe';
if (password_verify($pass, $encrypted)) { … }
A code-only summary of this chapter is available at 07.databases.summary.html