如何最好地将数据库与PHP应用程序分离?

8
我的问题是如何从应用程序的模型层中抽象出数据库连接?主要关注点是能够轻松地从不同类型的数据库进行更改。也许你一开始使用的是平面文件、逗号分隔数据库。然后你想转移到SQL数据库。然后稍后你决定使用LDAP实现会更好。一个人怎样才能轻松地计划这样的事情呢?
举个简单的例子,假设你有一个用户,有名字、姓氏和电子邮件地址。一个非常简单的PHP类表示它可能看起来像这样(请忽略公共实例变量的问题):
<?php

class User {
  public $first;
  public $last;
  public $email;
}

?>

我经常看到人们有一个DAO类,其中包含嵌入的SQL语句,如下所示:

<?php

class UserDAO {
  public $id;
  public $fist;
  public $last;
  public $email;

  public function create( &$db ) {
    $sql = "INSERT INTO user VALUES( '$first', '$last', '$email' )";
    $db->query( $sql );
  }
}

?>

我对这样的策略存在疑虑,因为当您想要更改数据库时,您必须更改每个 DAO 类的 create、update、load、delete 函数以处理新类型的数据库。即使您有一个自动生成它们的程序(我不特别喜欢),您也需要编辑此程序以使其现在工作。

您有什么解决方案来处理这个问题?

我的当前想法是创建一个 DAO 对象的超级类,具有其自己的 create、delete、update、load 函数。但是,这些函数将采用 DAO 属性数组并生成查询本身。通过这种方式,唯一的 SQL 在 SuperDAO 类中,而不是分散在几个类中。然后,如果您想要更改数据库层,您只需更改 SuperDAO 类如何生成查询。优点?缺点?可预见的问题?好的,坏的和丑陋的东西?

10个回答

9

您可以使用各种框架,如PDO、PEAR::MDB2或Zend_Db,但老实说,在12年的PHP开发中,我从未不得不从一种数据存储基础设施转换到另一种。

即使是从Sqlite到MySQL这样非常相似的东西,也极为罕见。如果您确实做了更多的事情,那么您将面临更大的问题。


我在开发中使用SQLite,生产环境下使用MySQL(使用Propel ORM),但目前我的个人项目非常小;我同意你所说的。 - analytik
在我的情况下,PDO无法帮助抽象数据库层。我需要使用mysql开发应用程序,但客户将他的数据库更改为oracle。由于两个数据库的SQL语法不同,PDO没有帮助。因此我遇到了很多麻烦。如果我使用两种类型的DAO(mysqldaos和oracledaos)来开发系统,例如,我可以通过工厂模式来管理想要应用哪一个。主要问题是现在我在mysql中以开发模式运行软件,而在生产中它在oracle中运行。 - jairhumberto

8

使用 ORM 通常是抽象数据库的首选方法。PHP 实现的不完整列表可在 Wikipedia 上找到。


3
我提出了一个有趣的概念,使开发人员能够创建与数据库无关的代码,但与ORM不同的是,它不会牺牲性能:
- 简单易用(像ORM一样) - 与SQL、NoSQL、文件等无关:db-agnostic - 如果供应商允许,则始终优化查询(子选择、映射-减少)
结果是 Agile Data - 数据库访问框架(详见视频说明)。
使用DB-agnostic代码和Agile Data解决实际任务
1. 首先描述业务模型。 2. 创建持久性驱动程序$db(一个花哨的词汇,表示数据库连接),可以是CSV文件、SQL或LDAP。 3. 将模型与$db关联并表达您的Action 4. 执行Action
在这一点上,框架将根据数据库的能力确定最佳策略,映射您的字段声明,为您准备并执行查询,以便您无需编写它们。
代码示例
我的下一个代码片段解决了一个相当复杂的问题,即确定所有VIP客户的当前总债务。模式:

enter image description here

接下来是与供应商无关的代码:

$clients = new Model_Client($db);
// Object representing all clients - DataSet

$clients -> addCondition('is_vip', true);
// Now DataSet is limited to VIP clients only

$vip_client_orders = $clients->refSet('Order');
// This DataSet will contain only orders placed by VIP clients

$vip_client_orders->addExpression('item_price')->set(function($model, $query){
    return $model->ref('item_id')->fieldQuery('price');
});
// Defines a new field for a model expressed through relation with Item

$vip_client_orders->addExpression('paid')
  ->set(function($model, $query){
    return $model->ref('Payment')->sum('amount');
});
// Defines another field as sum of related payments

$vip_client_orders->addExpression('due')->set(function($model, $query){
    return $query->expr('{item_price} * {qty} - {paid}');
});
// Defines third field for calculating due

$total_due_payment = $vip_client_orders->sum('due')->getOne();
// Defines and executes "sum" action on our expression across specified data-set

如果$db是SQL,则得到的查询:
select sum(
  (select `price` from `item` where `item`.`id` = `order`.`item_id` )
  * `order`.`qty`
  - (select sum(`payment`.`amount`) `amount` 
     from `payment` where `payment`.`order_id` = `order`.`id` )
) `due` from `order` 
where `order`.`user_id` in (
  select `id` from `user` where `user`.`is_client` = 1 and `user`.`is_vip` = 1 
)

对于其他数据源,执行策略可能会更加费力,但可以始终正常工作。
我认为我的方法是抽象数据库的好方法,并正在努力在MIT许可下实施它。

https://github.com/atk4/data


3

最好的方法是使用ORM(对象关系映射)库。PHP有很多这样的库。我个人使用并推荐doctrine orm(我将其与Silex一起使用,Silex是一个极简的PHP框架)。

这里有一个关于PHP ORM的StackOverflow帖子,如果您愿意,可以在其中找到一些替代方案:Good PHP ORM Library?


2
你应该研究一下PDO库
PDO提供了一个数据访问抽象层,这意味着无论你使用哪个数据库,你都可以使用相同的函数来发出查询和获取数据。
PDO不提供数据库抽象层;它不会重写SQL或模拟缺失的功能。如果需要此功能,您应该使用完整的抽象层。

那并没有将数据库抽象化,只是访问它。可以使用PDO来创建一个抽象层。 - Ben S
他在示例中没有将数据库抽象出来,只是通过预先创建的函数访问它。 - Ólafur Waage
我使用PDO。不幸的是,它只处理我已经能够处理的SQL数据库。 - Marshmellow1328

1
事实上,“数据访问逻辑应该在哪里实现?”这个话题的解决方案并不复杂。你需要记住的是,你的模型代码必须与你的数据访问代码分开。
例如:
模型层有一些业务逻辑,如User::name()方法。
class User 
{
  public $first;
  public $last;
  public $email;
  public function name ()
  {
      return $this->first." ".$this->last;
  }
}

数据访问层:
class Link
{
    $this->connection;
    public function __construct ()
    {
        $this->connection = PDO_Some_Connect_Function();
    }
    public function query ($query)
    {
        PDO_Some_Query ($this->connection, $query);
    }

}

class Database
{
    public $link;
    public function __construct ()
    {
        $this->link = new Link();
    }
    public function query ($query)
    {
        $this->link->query ($query);
    }
}

class Users
{
    public $database;
    public function __construct (&$database)
    {
         $this->database = &$database;
    } 
    public save ($user)
    {
        $this->database->link->query ("INSERT INTO user VALUES( '$user->first', '$user->last', '$user->email' ))";
    }

使用方法:

$database = new Database();

$users = new Users();

$users->save (new User());

在这个例子中,很明显你可以随时更改数据访问类Link,它将在任何查询上运行(这意味着您可以将用户保存在任何服务器上,只要您更改链接)。
同时,您拥有独立存在并且不知道谁在哪里保存其对象的清晰模型层代码。
此外,这里的数据库类似乎是不必要的,但实际上它可以产生很多想法,比如在一个项目中为多个数据库连接收集许多链接的一个实例。
还有一个单文件最简单而全能的框架db.php(http://dbphp.net),它建立在我描述的模式上,甚至可以自动创建表格,并完全控制其标准SQL字段/表格设置,并在每次需要时将数据库结构同步到您的模型。

1

理论上听起来不错,但很可能 YAGNI

最好使用 SQL 库,例如 PDO,在到达那里之前不要担心 LDAP。


这是一个常见的维护问题,最终很可能会需要。数据库模式很少是固定不变的。 - Ben S
他不是在谈论模式,而是在谈论如何在CSV和关系型数据库之间无缝切换。你有多经常这样做? - Greg
或者LDAP,或者任何新发明的数据库类型。如果我没有需求,我也不会提出这个问题。 - Marshmellow1328
即使更改数据库引擎很少见(比如从MySQL到Oracle),但可以通过使用数据库抽象层(例如PEAR :: MDB2)来处理它(尽管可能有CSV驱动程序,或者可以编写一个...)。 - Greg

1

一般来说,如果你要使用数据库,那么你的应用程序将受益于使用特定于“品牌”数据库的功能,并且将成为更稳固的应用程序。

很少从一个数据库系统转移到另一个数据库系统。唯一真正考虑实施此功能的时候是,如果你正在编写某种松散耦合的系统或框架,旨在大规模消费(如Zend Framework或Django)。


1

我一直喜欢使用ADOdb。从我所看到的来看,它似乎能够在不同平台之间进行快速切换。

http://adodb.sf.net


我们在工作中使用ADOdb进行抽象和ORM(在自定义MVC框架中),然后将其包装在我们的应用程序特定API中 - 这非常好。现在无法想象以其他方式完成它。 - codercake

0

Axon ORM 能够自动检测出模式的更改,而不需要您重新构建代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接