我有一系列的课程:
abstract class Database extends PDO {}
abstract class OracleDatabase extends Database {}
abstract class MySQLDatabase extends Database {}
abstract class MSSQLDatabase extends Database {}
然后当我想要一个数据库连接的实例时,我创建了一个扩展OracleDatabase
,MySQLDatabase
或MSSQLDatabase
的新类,具体取决于数据库所在的位置......例如
class MyAppDatabase extends OracleDatabase {}
首先,这是一个好的设计吗?我已经读过使用Interfaces而不是扩展抽象类更好,但我不知道如何在不重复代码的情况下做到这一点。
我确实意识到PDO的全部意义在于远离特定于数据库的代码,但是对于像DB对象包围这样的东西我仍然需要不同的功能(例如在Oracle中,你使用双引号;在MySQL中你使用反引号),数据类型检测(user_tab_columns vs INFORMATION_SCHEMA)等
并且 - 如果我想创建一个名为Updater
的类,让客户端为特定表创建一组多个“更新” - 使用相同的SET和WHERE子句字段,但不同的值,该怎么办?我不认为我能够从Database
类继承,所以我只是将它作为一个“HAS”数据库对象的独立类吗?
class Updater {
private $db;
private $table;
private $setFields;
private $whereFields;
private $updates;
function __construct($db, $table, $setFields, $whereFields) {
if (!($db instanceof Database)) {
if (is_scalar($db)) {
$type = gettype($db);
} else {
$type = get_class($db);
}
throw new Exception("Expected Database object; " . $type . " found.");
}
$this->db = $db;
// ensure $table is a table in the database
// ensure $setFields and $whereFields are columns in the table
$this->table = $table;
$this->setFields = $setFields;
$this->whereFields = $whereFields;
$this->updates = array();
}
function addUpdate($setValues, $whereValues) {
// ensure $setValues is an array and has the same cardinality as
// $this->setFields
// ensure $whereValues is an array and has the same cardinality as
// $this->whereFields
array_push($this->updates,
array(
'setValues'=>$setValues,
'whereValues' => $whereValues
)
);
}
function doUpdate() { // without error handling
$escTable = $this->db->bracket($table);
$setTemplate = array();
foreach ($this->setFields as $setField) {
$escField = $this->db->bracket($setField);
$colonField = makeColonField($setField); // :fieldName
$setting = "$escField = $colonField";
array_push($setTemplate, $setting);
}
$csvSetTemplate = implode(", ", $setTemplate);
$whereTemplate = array();
foreach ($this->whereFields as $whereField) {
$escField = $this->db->bracket($whereField);
$colonField = makeColonField($setField); // :fieldName
$setting = "$escField = $colonField";
array_push($whereTemplate, $setting);
}
$andedWhereTemplates = implode(" AND ", $whereTemplate);
$sql = "UPDATE $escTable SET $csvSetTemplate WHERE $andedWhereTemplates";
$sth = $this->db->prepare($sql);
foreach ($this->updates as $update) {
$setValues = $update['setValues'];
$whereValues = $update['whereValues'];
$params = array();
for ($i=0; $i<count($setValues); $i++) {
$setField = $this->setFields[$i];
$setValue = $setValues[$i];
$colonField = makeColonField($setField);
$params[$colonField] = $setValue;
}
for ($i=0; $i<count($whereValues); $i++) {
$whereField = $this->whereFields[$i];
$whereValue = $whereValues[$i];
$colonField = makeColonField($whereField);
$params[$colonField] = $whereValue;
}
$sth->execute($params);
}
}
}
这是一个很好的解决方案吗?
答案 0 :(得分:1)
首先,我建议在这种情况下抽象类更好,因为不同的RDBMS之间通常有许多共同点,你可以用它们编写一些常用的方法来更清楚地解决问题。
还有另一种方法可以做到这一点:
class Database extends PDO {
private $engine; //this is a DBEngine
}
interface DBEngine
{
}
class MySQLEngine implements DBEngine
{
}
class MSSQLEngine implements DBEngine
{
}
...
在这种情况下,您可以使用接口,因为在数据库中实现了常用方法,并且每个引擎只实现了在RDBMS之间具有不同行为的方法。
这称为适配器模式,您可以在CodeIgniter的源代码中阅读一些相关内容:http://ellislab.com/codeigniter。
对于第二个问题,您可以在类Database中实现更新方法。
换句话说,使“更新”成为类Database的公共成员。
答案 1 :(得分:1)
我更倾向于拥有一个数据库包装类,然后注入一个“提供者”(一个提供者是MySQL,Oracle或其他一些数据库引擎),然后通过你的包装类与你的数据库交互。
您有一个引导程序文件可以设置它们,然后将新创建的数据库对象实例传递给您的应用程序。
这称为依赖注入。理论上,您可以轻松地将数据库引擎切换到另一个引擎,甚至可以注入测试数据库并使用相同的应用程序。
一个快速的,袖手旁观的例子:
<?php
class Database
{
private $database;
public function setProvider(DatabaseProvider $database)
{
$this->database = $database;
}
public function select($table, $fields = array(), $conditions = array(), $order = array())
{
$this->database->select($table, $fields, $conditions, $order);
}
public function insert($table, $values)
{
$this->database->insert($table, $values);
}
public function delete($table, $conditions = array())
{
$this->database->delete($table, $conditions);
}
}
然后是一个示例提供者:
<?php
class MySQL implements DatabaseProvider
{
public function __construct($config)
{
// create PDO instance with settings in $config
$this->connection = new PDO();
}
public function select($table, $fields = array(), $conditions = array(), $order = array())
{
// build SELECT statement based on table, and other parameters passed
// return result set
}
// and so on...
}
您的MySQL
实例将在您的引导程序文件中创建,您也可以在其中传递连接设置。
显然上面的内容并不是一个完整的例子,但应该足以让你开始充实自己的解决方案。
<强>加成强>
示例目录结构,受Symfony 2的启发。
/app
/config
database.xml
/src
/VendorName
/AppName
/Database
/Provider
DatabaseInterface.php
MSSQL.php
MySQL.php
Oracle.php
Database.php
Application.php
Bootstrap.php