PDOで接続する
PHPからPostgreSQLへ接続するにはPDOとpdo_pgsql拡張が必要です。接続情報は環境変数などで管理し、公開リポジトリへパスワードを置きません。
db.phpPHP
<?php
$dsn = 'pgsql:host=127.0.0.1;port=5432;dbname=study_postgres';
$pdo = new PDO($dsn, $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);接続エラーは、拡張の有無、ホスト、ポート、DB名、ロール、パスワードの順に切り分けます。
プレースホルダーで検索する
products.phpPHP
<?php
require_once __DIR__ . '/db.php';
$minPrice = 500;
$stmt = $pdo->prepare(
'SELECT name, price FROM products
WHERE price >= :min_price
ORDER BY price DESC'
);
$stmt->execute(['min_price' => $minPrice]);
foreach ($stmt as $product) {
echo htmlspecialchars($product['name'], ENT_QUOTES, 'UTF-8');
echo ': ' . $product['price'] . '円<br>';
}ブラウザーの表示例OUTPUT
PostgreSQL入門書: 3280.00円
マグカップ: 900.00円numeric(10, 2)の値は、PDOから文字列として"3280.00"のように取得されることがあります。SQLインジェクション対策のプレースホルダーと、HTML出力時のエスケープは別々に行います。
入力を検証して追加する
入力値を確認してINSERTPHP
$name = trim($_POST['name'] ?? '');
$price = filter_input(INPUT_POST, 'price', FILTER_VALIDATE_FLOAT);
if ($name === '' || $price === false || $price < 0) {
throw new InvalidArgumentException('商品名と価格を確認してください。');
}
$stmt = $pdo->prepare(
'INSERT INTO products (name, price, stock)
VALUES (:name, :price, :stock)
RETURNING id, name, price'
);
$stmt->execute(['name' => $name, 'price' => $price, 'stock' => 0]);
$product = $stmt->fetch();追加結果の例OUTPUT
id: 7
name: 付せん
price: 250.00PHPからトランザクションを使う
在庫と注文を一緒に更新するPHP
try {
$pdo->beginTransaction();
$stmt = $pdo->prepare(
'UPDATE products SET stock = stock - :quantity
WHERE id = :id AND stock >= :quantity'
);
$stmt->execute(['quantity' => 1, 'id' => 1]);
if ($stmt->rowCount() !== 1) {
throw new RuntimeException('在庫がありません。');
}
$stmt = $pdo->prepare(
'INSERT INTO orders (product_id, quantity, ordered_at)
VALUES (:product_id, :quantity, CURRENT_DATE)'
);
$stmt->execute(['product_id' => 1, 'quantity' => 1]);
$pdo->commit();
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $e;
}成功時の結果OUTPUT
UPDATE: 1 row
INSERT: 1 row
COMMIT: 完了どちらかが失敗したらロールバックし、在庫だけが減る状態を残しません。利用者向けのエラーとログ用の詳細を分けます。
安全に運用する
- DBロールへ必要最小限の権限だけを与える
- 接続情報を環境変数や秘密管理へ分ける
- 入力検証、プレースホルダー、出力エスケープを分ける
- テスト用と本番用のDBを分ける
- 例外へパスワードやSQL全文を表示しない
入力エラーの表示例OUTPUT
商品名と価格を確認してください。