PostgreSQL

PHPからPostgreSQLを使う

PDOで接続し、安全に検索・追加・更新します。

PostgreSQL入門の講座一覧

01

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名、ロール、パスワードの順に切り分けます。

02

プレースホルダーで検索する

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出力時のエスケープは別々に行います。

03

入力を検証して追加する

入力値を確認して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.00
04

PHPからトランザクションを使う

在庫と注文を一緒に更新する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: 完了

どちらかが失敗したらロールバックし、在庫だけが減る状態を残しません。利用者向けのエラーとログ用の詳細を分けます。

05

安全に運用する

  • DBロールへ必要最小限の権限だけを与える
  • 接続情報を環境変数や秘密管理へ分ける
  • 入力検証、プレースホルダー、出力エスケープを分ける
  • テスト用と本番用のDBを分ける
  • 例外へパスワードやSQL全文を表示しない
入力エラーの表示例OUTPUT
商品名と価格を確認してください。