在为不同的数据库引擎实现数据处理器时如何防止重复?

时间:2013-01-01 18:35:51

标签: php sql oop datamapper data-mapping

在我的项目中,我想构建对多个数据库引擎的支持。我通过放置在模型层中的datamappers实现了这一点。一个简单的例子看起来像(对不起代码墙,如果你想获得要点,请跳到最后):

用户

namespace Application\Model;

use Application\Model\Mapper;

class User
{
    private $mapper;

    private $id;

    public function __construct(Mapper $mapper, $id)
    {
        $this->mapper = $mapper;
    }

    public function setPassword($password)
    {
        $this->mapper->updatePassword($this->id, $password);
    }
}

Mapper界面

namespace Application\Model;

interface UserMapper
{
    public function updatePassword($id, $password);
}

MySQL映射器

namespace Application\Model;

use Application\Model\Mapper;

class UserMysqlMapper implements Mapper
{
    private $connection;

    public function __construct(\PDO $connection)
    {
        $this->connection = $connection;
    }

    public function updatePassword($id, $password)
    {
        $stmt = $this->connection->prepare('UPDATE user SET password = :password WHERE userid = :userid');
        $stmt->execute(['password' => $password, 'userid' => $id]);
    }
}

PostgreSQL mapper

namespace Application\Model;

use Application\Model\Mapper;

class UserPgsqlMapper implements Mapper
{
    private $connection;

    public function __construct(\PDO $connection)
    {
        $this->connection = $connection;
    }

    public function updatePassword($id, $password)
    {
        $stmt = $this->connection->prepare('UPDATE user SET password = :password WHERE userid = :userid');
        $stmt->execute(['password' => $password, 'userid' => $id]);
    }
}

加载内容

$connection = new \PDO(dsn stuff);
$mapper = \Application\Model\UserPgsqlMapper($connection);
$user = \Application\Model\User($mapper, 1);
$user->setPassword('new password');

正如您所看到的,我有两个映射器基本上具有重复的代码(两个引擎的查询都相同)。这有点“强奸”DRY原则,但是我没有看到一个好/干净/正确的方法来防止这种情况。请注意,这当然只是一个简单的示例,通常会有不同数据库引擎之间的查询相同。

我已经考虑让映射器使用基本查询扩展一些映射器,但是这感觉更脏,因为根本不能对某些东西进行基本查询。

我昨天在PHP聊天中也问了这个问题,结论基本上是“复制并继续你的生活”,我想的越多,我认为这是我唯一真正的选择。

但是为了确保我没有错过一些简洁明智的解决方案,我想我会在这里发一个问题。

2 个答案:

答案 0 :(得分:1)

如果示例代码具有代表性,则复制是真实的代码气味。您不仅要复制查询,而且PHP代码也是相同的;你在这里写了很多重复的代码,并没有获得它的保留。

我会考虑将查询视为资源,而不是代码。

例如,创建一个名为“queries.php”的文件,并将每个查询设置为变量:

$update_password= ["default" => "UPDATE user SET password = :password WHERE userid = :userid"]
$create_user = ["default" => "insert into blabla"
                "mysql"   => "insert into wibble"]

执行查询时,请检查是否存在特定于数据库的版本,否则请使用默认值。

您也可以考虑映射器类是否真正获得了保留 - 如果他们所做的只是执行稍微不同的SQL语句,您可能能够摆脱它们,或者至少将大量代码拉入到超级。

例如,您可以使用默认行为创建一个datamapper类,而不是接口,并提取适合当前数据库的查询。如果特定数据库确实需要不同的方法实现,则可以在特定于数据库的子类中覆盖该方法。

类似的东西:

namespace Application\Model;

use Application\Model\Mapper;

class UserDefaultMapper
{
    private $connection;

    public function __construct(\PDO $connection)
    {
        $this->connection = $connection;
    }

    public function updatePassword($id, $password)
    {
        $query = getQueryForDB("updatePassword", $connection);

        $stmt = $this->connection->prepare(query);
        $stmt->execute(['password' => $password, 'userid' => $id]);
    }
    public function createUser($name){
    ...
    }
}

如果“createUser()”需要特定于数据库的实现(例如,要检索用户ID),则应创建覆盖:

namespace Application\Model;

use Application\Model\Mapper;

class UserMySQLMapper extends UserDefaultMapper
{
    public function createUser($name){
    ...
    }
}

这为您提供了更少的代码,更少的重复代码(连接管理,语句执行等),允许通过资源文件而不是继承来管理最常见的变体(针对不同引擎的不同查询),但仍然为您提供在需要时可以覆盖的权力。

答案 1 :(得分:0)

  

我已经考虑过让映射器扩展一些mapper   基本查询,但这感觉更脏,因为简单   不能作为基础查询。

从PHP 5.4.0开始,PHP实现了一种名为Traits的代码重用方法。 Trait旨在通过使开发人员能够在生活在不同类层次结构中的几个独立类中自由地重用方法集来减少单个继承的某些限制。

此外,Flourish PHP Unframework能够在不同的数据库(MySQL,PostgreSQL,SQLite,MSSQL,Oracle,DB2)上运行。它包括对所有数据库类型的one dialect of SQL的支持。您可以直接在项目中使用它,或者绘制通用SQL方言的概念。

最后,您甚至可以将这两种方法(特征和SQL方言)结合起来。对于简单查询,请使用SQL的子集,而对于高级查询,请“混合”来自不同特征的查询。

不过,我一直在使用Flourish来处理MySQL,PostgreSQL,SQLite和MS SQL Server等数据库上的许多非平凡项目。我建议直接使用它。


更新:您也可以参考MediaWiki中的database abstraction layer。它为不同的后端构建SQL查询,但保留了相同的界面供应用程序使用。