3

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

用户

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);
    }
}

映射器接口

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 映射器

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 聊天中问过这个问题,结论基本上是“伪造重复并继续你的生活”,我越想越觉得这是我唯一真正的选择。

但为了确保我没有错过一些干净而聪明的解决方案,我想我会在这里发布一个问题。

4

2 回答 2

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 语句,您可能能够摆脱它们,或者至少将大量代码提取到超类中。

例如,您可以创建一个具有默认行为的数据映射器类而不是接口,并拉入适合当前数据库的查询。如果特定数据库确实需要方法的不同实现,您可以在特定于数据库的子类中覆盖该方法。

就像是:

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){
    ...
    }
}

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

于 2013-01-01T22:52:50.867 回答
0

我曾考虑让映射器使用基本查询扩展一些映射器,但这感觉更脏,因为根本不可能有某事的基本查询。

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

此外,Flourish PHP Unframework 能够在不同的数据库(MySQL、PostgreSQL、SQLite、MSSQL、Oracle、DB2)上运行。它包括对所有数据库类型的一种 SQL 方言的支持。你可以直接在你的项目中使用它,或者借鉴它的通用 SQL 方言的想法。

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

顺便说一句,我在 MySQL、PostgreSQL、SQLite 和 MS SQL Server 等数据库上的许多重要项目中都使用了 Flourish。我建议直接使用它。


更新:您也可以参考 MediaWiki 中的数据库抽象层。它为不同的后端构建 SQL 查询,但为应用程序使用保持相同的接口。

于 2013-01-02T09:58:48.117 回答