php - 如何验证数据映射器模式中的唯一性?

标签 php sql oop datamapper

使用数据映射器模式:

  • 对象/实体不知道数据映射器和存储(例如 RDBMS)。
  • 存储不知道数据映射器和对象/实体。
  • 数据映射器当然知道并桥接对象/实体和存储。

如何在不了解数据映射器和存储(即 $user)的情况下验证对象/实体中的唯一字段(例如 $user->name)不能简单地调用 $userDataMapper->count('name='.$this->name))?

class User
{
    private $name; // unique

    public function validate(): bool
    {
        // what to put here to validate that $this->name
        // is unique in column `users`.`name`?
    }
}

到目前为止,我知道有两种可能的解决方案(一种是由 tereško 提出的),但它们都有缺点。

按照 tereško 的建议,第一个是捕获 PDOException。

class UserDataMapper
{
    public function store($user)
    {
        $sql = 'INSERT INTO `users` SET `name` = :name, `email_address` = :emailAddress...';
        $params =
        [
            'name' => $user->getName(),
            'emailAddress' => $user->getEmailAddress(),
            // ...
        ];

        $statement = $this->connection->prepare($sql);

        try
        {
            $statement->execute($params);
        }
        catch (\PDOException $e)
        {
            if ($e->getCode() === 23000)
            {
                // problem: can only receive one unique error at a time.

                // parse error message and throw corresponding exception.
                if (...name error...)
                {
                    thrown new \NameAlreadyRegistered;
                }
                elseif (...email address error...)
                {
                    thrown new \EmailAlreadyRegistered;
                }
            }

            throw $e; // because if this happens, you missed something
        }
    }
}

// Controller
class Register
{
    public function run()
    {
        if ($user->validate()) // first step of validation
        {
            // second step of validation
            try
            {
                $this->userDataMapper->store($this->user);
            }
            catch (\NameAlreadyRegistered $e)
            {
                $this->errors->add(... NameAlreadyRegistered ...)
            }
            catch (\EmailAlreadyRegistered $e)
            {
                $this->errors->add(... EmailAlreadyRegistered ...)
            }
            // ...other catches...
        }
        else
        {
            $this->errors = $user->getErrors();
        }
    }
}

问题是这会将验证拆分到两个地方,即在实体(用户)和 DataMapper/Controller 内(由 DataMapper 检测并传递给 Controller 进行记录)。或者,DataMapper 可以捕获并处理异常/MySQL 错误代码,但这违反了单一责任原则,同时没有缓解“拆分验证”问题。

此外,PDO/MySQL 一次只能抛出一个错误。如果有两个或多个唯一列,我们一次只能“验证”其中一个。

在两个地方拆分验证的另一个结果是,如果稍后我们想要添加更多唯一列,那么除了 User 实体之外,我们还必须修改 Register Controller (以及 ChangeEmailAddress 和 ChangeProfile Controller 等)。

第二种方法是我目前正在使用的方法,即将验证分离到一个单独的对象中。

Class UserValidation
{
    public function validate()
    {
        if ($this->userDataMapper->count('name='.$user->getName() > 0))
        {
            $this->errors->add(...NameAlreadyRegistered...);
        }

        if ($this->userDataMapper->count('email_address='.$user->getEmailAddress() > 0))
        {
            $this->errors->add(...EmailAlreadyRegistered...);
        }
    }
}

// Controller
class Register
{
    public function run()
    {
        if ($this->userValidation()->validate())
        {
            $this->userDataMapper()->store($user);
        }
        else
        {
            $this->errors = $this->userValidation()->getErrors();
        }
    }
}

这行得通。直到实体被扩展。

class SpecialUser extends User
{
    private $someUniqueField;
}

// need to extend the UserValidation to incorporate the new field(s) too.
class SpecialUserValidation extends UserValidation
{
    public function validate()
    {
        parent::validate();

        // ...validate $this->user->someUniqueField...
    }
}

对于每个实体子类,都需要一个验证子类。

所以,我们回到我最初的问题。如何(正确地)验证数据映射器模式中的唯一性?

最佳答案

您为什么要做 RDBMS 的工作?除非您正在使用一些过时的 SQL 连接抽象 API(例如已死但未被遗忘的 ext/mysql),否则试图违反 UNIQUE 约束将导致异常被抛出。

因此,您的数据映射器应该只捕获该异常(假设使用 PDO,所以它将是 PDOException),找出错误代码,然后将其作为适当的业务域重新抛出异常(exception)。就是这样。

然后可以在服务层处理该域异常。

您的数据映射器不应该负责数据完整性检查。这些由 RDBMS 的 CONSTRAINT 定义处理。可用约束的范围当然取决于您使用的 RDBMS。

namespace Model\Mapper;

use Model\Entity;
use Model\Exception;
use Component\DataMapper

class User extends DataMapper 
{
    // DB $this->connection passing is probably shared, so it's nice to just leave it in superclass

    public function store(Entity\User $user)
    {
        $statement = $this->connection->prepare('INSERT INTO ...');
        $statement->bindValue(':email', $user->getEmailAddress());
        try {
            $statement->execute();
        } catch (\PDOException $e) {
            if ($e->getCode() === 23000) {
                thrown new Exception\EmailAlreadyRegistered;
            }
            throw $e; // because if this happens, you missed something
        }
    }

}

关于php - 如何验证数据映射器模式中的唯一性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47942450/

相关文章:

php - Wordpress 插件通过后端表单将数据插入数据库

javascript - 使用 PHP 和 Bootstrap CSS 从复选框获取状态

php - 制作动态下拉导航系统

php - 我可以在 PHP 中重载方法吗?

ios - 使用我的自定义 init 方法访问实例化对象的属性

java - 搜索动物类别的动物方法

php - 必需的 @OA\Info() 未找到异常在 laravel 8.x 中不起作用

sql - 通用 SQL 查询监听器

mysql - 按 MySQL 表中的平均评分排序的艺术家姓名列表

sql - 如何在 Oracle 中加速 row_number?