PHPでMySQLのinsertとupdateで使えるPDOのprepared statementを生成するプログラムを書いてみました。使用頻度の高そうなパターンのSQLを生成することを目標にしてプログラムを書きました。
SQLBuilderクラス
メインとなるクラスです。SQLBuilderクラスの内部で使っているColumnインタフェースとColumnインタフェースを実装したBindableColumn、FixedValueクラスは、SQLBuilderクラスの内部のみで使用することを想定しているので、SQLBuilder使用時に意識する必要はありません。
<?php
class SQLBuilder {
private string $table;
/**
* @var Column[]
*/
private array $columns = [];
public function __construct($table) {
$this->table = $table;
}
public function add($column, $type): self
{
$this->columns[] = new BindableColumn($column, $type);
return $this;
}
public function addFixedValue($column, $value): self
{
$this->columns[] = new FixedValue($column, $value);
return $this;
}
public function buildStatementForInsert()
{
$targetColumns = implode(', ', array_map(fn(Column $column) => $column->getName(),$this->columns));
$targetValues = implode(', ', array_map(fn(Column $column) => $column->buildLineForInsert(), $this->columns));
return 'INSERT INTO '.$this->table.'('.$targetColumns.') VALUES ('.$targetValues.')';
}
// update用のPrepared Statement生成メソッドは一般化のため、多少複雑になっています。
// idカラムを1つ指定するといったように、用途が決まっている場合は単純化するのもありです。
public function buildStatementForUpdate($whereColumnNames)
{
$whereColumnNames = array_flip($whereColumnNames);
$updateColumnsArray = [];
$whereColumnsArray = [];
foreach($this->columns as $column)
{
if(isset($whereColumnNames[$column->getName()]))
{
$whereColumnsArray[] = $column;
}
else
{
$updateColumnsArray[] = $column;
}
}
$updateColumns = implode(', ', array_map(fn(Column $column) => $column->buildLineForUpdate(), $updateColumnsArray));
$whereColumns = implode(', ', array_map(fn(Column $column) => $column->buildLineForUpdate(), $whereColumnsArray));
return 'UPDATE '.$this->table.' SET '.$updateColumns.' WHERE '.$whereColumns;
}
// 一度生成したPrepared Statementを再利用して、別の値をbindするためのメソッド
public function bindValueToStatement($value, \PdoStatement $stmt)
{
foreach ($this->columns as $column)
{
$column->bindValueTo($value, $stmt);
}
}
// 一回だけINSERT文を実行するためのショートカットメソッド
public function executeInsert(\PDO $pdo, $value)
{
$stmt = $pdo->prepare($this->buildStatementForInsert());
$this->bindValueToStatement($value, $stmt);
$stmt->execute();
}
// 一回だけUPDATE文を実行するためのショートカットメソッド
public function executeUpdate(\PDO $pdo, $value, array $whereColumnNames)
{
$stmt = $pdo->prepare($this->buildStatementForUpdate($whereColumnNames));
$this->bindValueToStatement($value, $stmt);
$stmt->execute();
}
}
<?php
interface Column {
public function getName();
public function buildLineForInsert();
public function buildLineForUpdate();
public function bindValueTo($value, \PdoStatement $stmt);
}
<?php
class BindableColumn implements Column {
private string $name;
private int $type;
public function __construct(string $name, int $type) {
$this->name = $name;
$this->type = $type;
}
public function getName() {
return $this->name;
}
public function buildLineForInsert() {
return ':'.$this->name;
}
public function buildLineForUpdate() {
return $this->name.' = :'.$this->name;
}
public function bindValueTo($value, \PdoStatement $stmt) {
$stmt->bindValue(':'.$this->name, $value[$this->name], $this->type);
}
}
<?php
class FixedValue implements Column{
private string $name;
private $value;
public function __construct(string $name, $value) {
$this->name = $name;
$this->value = $value;
}
public function getName() {
return $this->name;
}
public function buildLineForInsert() {
return $this->value;
}
public function buildLineForUpdate() {
return $this->value;
}
public function bindValueTo($value, \PdoStatement $stmt) {
}
}
使用例
まずはINSERT文の生成例です。
<?php
// INSERT文の生成
$builder = new SQLBuilder('table');
$builder
->add('column1', \PDO::PARAM_INT)
->add('column2', \PDO::PARAM_INT)
->addFixedValue('date', 'NOW()');
// INSERT INTO table(column1, column2, date) VALUES (:column1, :column2, NOW())
$sql = $builder->buildStatementForInsert();
// Prepared Statementに値をbindして実行
// 一回だけのbindならSQLBuilder::executeInsertメソッドを使うのもあり
$pdo = new \PDO('.....');
$value = ['column1' => 68342, 'column2' => 'ABC'];
$stmt = $pdo->prepare($sql);
$builder->bindValueToStatement($value, $stmt);
$stmt->execute();
続いてUPDATE文の生成例です。
<?php
// UPDATE文の生成
$builder = new SQLBuilder('table');
$builder
->add('column1', \PDO::PARAM_INT)
->add('column2', \PDO::PARAM_INT)
->add('id', \PDO::PARAM_INT)
->addFixedValue('date', 'NOW()');
// UPDATE table SET column1 = :column1, column2 = :column2, NOW() WHERE id = :id
$sql = $builder->buildStatementForUpdate(['id']);
// Prepared Statementに値をbindして実行
// 一回だけのbindならSQLBuilder::executeUpdateメソッドを使うのもあり
$pdo = new \PDO('.....');
$value = ['column1' => 68342, 'column2' => 'ABC', 'id' => 5638];
$stmt = $pdo->prepare($sql);
$builder->bindValueToStatement($value, $stmt);
$stmt->execute();
コメント