利用 ReflectionClass 動態讀取物件屬性,自動組裝 INSERT 語句,減少手寫 SQL 錯誤,適合簡易 ORM 或 ActiveRecord 基礎類別。
<?php
class BaseModel {
// 子類別需定義對應表名或由命名慣例推導
}
class Db {
private PDO $pdo;
public function insertModel(BaseModel $model, bool $returnId = false): int|bool {
$class = get_class($model);
$table = $this->tableName($class); // 如 user_model → users
$refl = new ReflectionClass($class);
$props = $refl->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED);
// 略過靜態/主鍵等,視需求調整
$fields = []; $placeholders = []; $values = [];
foreach ($props as $prop) {
$name = $prop->getName();
if ($name === 'id') continue; // 假設自增主鍵
$prop->setAccessible(true);
$val = $prop->getValue($model);
if ($val === null) continue; // 略過 null
$fields[] = "`$name`";
$placeholders[] = '?';
$values[] = $val;
}
$sql = "INSERT INTO `$table` (" . implode(',', $fields) . ") VALUES (" . implode(',', $placeholders) . ")";
$stmt = $this->pdo->prepare($sql);
$ok = $stmt->execute($values);
if ($returnId) return $ok ? (int)$this->pdo->lastInsertId() : 0;
return $ok ? $stmt->rowCount() : false;
}
private function tableName(string $class): string {
// 簡單慣例:UserModel → users
return strtolower(preg_replace('/Model$/', '', $class)) . 's';
}
}
?>
<?php
class UserModel extends BaseModel {
public $username;
public $email;
public $password_hash;
public $created_at = 'NOW()'; // 特殊處理或由 DB 預設值
}
$user = new UserModel();
$user->username = 'john';
$user->email = 'john@example.com';
$user->password_hash = password_hash('secret', PASSWORD_DEFAULT);
$db = new Db($pdo);
$newId = $db->insertModel($user, true);
echo "新增 ID: $newId";
?>
| 優點 | 缺點 | 改進 |
|---|---|---|
| 零配置、自動同步屬性 | 反射效能較慢 (可快取 ReflectionProperty 陣列) | 靜態快取 $refl->getProperties() |
| 支援 protected/private (setAccessible) | 無法處理關聯、複雜型別、預設值邏輯 | 引入屬性註解 (PHP 8 Attribute) 標註欄位對應 |
| 程式碼集中、易維護 | SQL 注入風險低 (參數化),但欄位名未轉義 | 欄位名白名單或反引號包裹 |
<?php
#[\Attribute]
class Column {
public function __construct(public string $name = '', public bool $primary = false, public bool $autoIncrement = false) {}
}
class UserModel {
#[Column(name: 'id', primary: true, autoIncrement: true)]
public int $id;
#[Column(name: 'user_name')]
public string $username;
// ...
}
?>
搭配 ReflectionProperty::getAttributes(Column::class) 即可精準控制欄位對應、主鍵略過、型別轉換等。