当前位置: 首页 > 知识库问答 >
问题:

PHP中正确的存储库模式设计?

麹渊
2023-03-14

前言:我试图在关系数据库的MVC体系结构中使用存储库模式。

我最近开始学习PHP中的TDD,我意识到我的数据库与应用程序的其余部分耦合得太紧密了。我读过关于存储库和使用IoC容器将其“注入”到我的控制器中的文章。很酷的东西。但现在有一些关于存储库设计的实际问题。请考虑以下示例。

<?php

class DbUserRepository implements UserRepositoryInterface
{
    protected $db;

    public function __construct($db)
    {
        $this->db = $db;
    }

    public function findAll()
    {
    }

    public function findById($id)
    {
    }

    public function findByName($name)
    {
    }

    public function create($user)
    {
    }

    public function remove($user)
    {
    }

    public function update($user)
    {
    }
}

所有这些查找方法都使用选择所有字段(select*)方法。然而,在我的应用程序中,我总是试图限制我得到的字段的数量,因为这经常增加开销和放慢速度。对于那些使用这种模式的人,你如何处理这个?

虽然这个类现在看起来很不错,但我知道在一个真实的应用程序中,我需要更多的方法。例如:

  • FindAllByNameAndStatus
  • FindallinCountry
  • FindAllWithEmailAddressSet
  • FindAllByAgeandGender
  • FindAllByAgeAndGenderOrderByAge

正如您所看到的,可能有一个非常非常长的方法列表。如果添加了上面的字段选择问题,问题就会恶化。在过去,我通常只是把所有这些逻辑正确地放在我的控制器中:

<?php

class MyController
{
    public function users()
    {
        $users = User::select('name, email, status')
            ->byCountry('Canada')->orderBy('name')->rows();

        return View::make('users', array('users' => $users));
    }
}
<?php

class MyController
{
    public function users()
    {
        $users = $this->repo->get_first_name_last_name_email_username_status_by_country_order_by_name('Canada');

        return View::make('users', array('users' => $users))
    }

}

这使我认为存储库应该只有固定数量的方法(如save()remove()find()findall()等)。但是我如何运行特定的查找呢?我听说过规范模式,但在我看来,这只是减少了一整组记录(通过issatisfiedby()),如果从数据库中提取,这显然会产生主要的性能问题。

显然,在使用存储库时,我需要重新考虑一些事情。谁能告诉我们如何处理这件事是最好的呢?

共有1个答案

秦信瑞
2023-03-14

我想我应该试着回答我自己的问题。以下只是解决我原问题中问题1-3的一种方法。

免责声明:在描述模式或技术时,我可能并不总是使用正确的术语。很抱歉。

  • 创建用于查看和编辑用户的基本控制器的完整示例。
  • 所有代码都必须是完全可测试和可模拟的。
  • 控制器应该不知道数据存储在哪里(这意味着可以更改数据)。
  • 显示SQL实现(最常见)的示例。
  • 为了获得最大的性能,控制器应该只接收他们需要的数据-没有额外的字段。
  • 实现应利用某种类型的数据映射器,以便于开发。
  • 实现应具有执行复杂数据查找的能力。

R(读)不是那么容易的。这里没有模型,只是值对象。如果愿意,可以使用数组。这些对象可能代表一个模型或多个模型的混合,任何真正的东西。这些本身并不是很有趣,但它们是如何生成的。我正在使用我称之为查询对象的东西。

让我们简单地从我们的基本用户模型开始。请注意,根本没有ORM扩展或数据库内容。只是纯粹的模特荣耀。添加gettersetters验证等等。

class User
{
    public $id;
    public $first_name;
    public $last_name;
    public $gender;
    public $email;
    public $password;
}

在我创建我的用户存储库之前,我想创建我的存储库接口。这将定义存储库必须遵循的“契约”,以便被我的控制器使用。记住,我的控制器不会知道数据实际存储在哪里。

interface UserRepositoryInterface
{
    public function find($id);
    public function save(User $user);
    public function remove(User $user);
}
class SQLUserRepository implements UserRepositoryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function find($id)
    {
        // Find a record with the id = $id
        // from the 'users' table
        // and return it as a User object
        return $this->db->find($id, 'users', 'User');
    }

    public function save(User $user)
    {
        // Insert or update the $user
        // in the 'users' table
        $this->db->save($user, 'users');
    }

    public function remove(User $user)
    {
        // Remove the $user
        // from the 'users' table
        $this->db->remove($user, 'users');
    }
}

现在,我们的存储库处理了CUD(创建、更新、删除),我们可以关注R(读取)。查询对象只是某种类型的数据查找逻辑的封装。它们不是查询生成器。通过像我们的存储库一样抽象它,我们可以更容易地更改它的实现和测试它。查询对象的一个示例可能是AllUserSqueryAllActiveUserSquery,甚至是MostCommonUserFirstNames

您可能会想“难道我就不能在我的存储库中为那些查询创建方法吗?”是的,但我不这么做的原因是:

  • 我的存储库是用来处理模型对象的。在一个真实的应用程序中,如果我想列出所有的用户,为什么我需要获得password字段?
  • 存储库通常是特定于模型的,但查询通常涉及多个模型。那么,您将方法放在什么存储库中?
  • 这使我的存储库非常简单--而不是一个臃肿的方法类。
  • 所有查询现在都组织到它们自己的类中。
  • 实际上,在这一点上,存储库的存在只是为了抽象我的数据库层。

对于我的示例,我将创建一个查询对象来查找“AllUsers”。界面如下:

interface AllUsersQueryInterface
{
    public function fetch($fields);
}

这是我们可以再次使用数据映射器来帮助加快开发的地方。注意,我允许对返回的数据集进行一个调整-字段。这就是我想要对执行的查询进行操作的范围。请记住,我的查询对象不是查询生成器。它们只是执行一个特定的查询。但是,由于我知道我可能会在许多不同的情况下大量使用这一项,所以我给了自己指定字段的能力。我从来不想归还我不需要的田地!

class AllUsersQuery implements AllUsersQueryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function fetch($fields)
    {
        return $this->db->select($fields)->from('users')->orderBy('last_name, first_name')->rows();
    }
}

在继续讨论控制器之前,我想展示另一个例子来说明这是多么强大。可能我有一个报告引擎,需要为AlloverdueAccounts创建一个报告。对于我的数据映射器,这可能会很棘手,在这种情况下,我可能希望编写一些实际的SQL。没问题,下面是这个查询对象的样子:

class AllOverdueAccountsQuery implements AllOverdueAccountsQueryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function fetch()
    {
        return $this->db->query($this->sql())->rows();
    }

    public function sql()
    {
        return "SELECT...";
    }
}

现在是有趣的部分--把所有的片段集合在一起。注意,我使用的是依赖注入。通常,依赖项被注入构造函数,但实际上我更喜欢将它们直接注入我的控制器方法(路由)。这最小化了控制器的对象图,我实际上发现它更易读。注意,如果您不喜欢这种方法,只需使用传统的构造函数方法即可。

class UsersController
{
    public function index(AllUsersQueryInterface $query)
    {
        // Fetch user data
        $users = $query->fetch(['first_name', 'last_name', 'email']);

        // Return view
        return Response::view('all_users.php', ['users' => $users]);
    }

    public function add()
    {
        return Response::view('add_user.php');
    }

    public function insert(UserRepositoryInterface $repository)
    {
        // Create new user model
        $user = new User;
        $user->first_name = $_POST['first_name'];
        $user->last_name = $_POST['last_name'];
        $user->gender = $_POST['gender'];
        $user->email = $_POST['email'];

        // Save the new user
        $repository->save($user);

        // Return the id
        return Response::json(['id' => $user->id]);
    }

    public function view(SpecificUserQueryInterface $query, $id)
    {
        // Load user data
        if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
            return Response::notFound();
        }

        // Return view
        return Response::view('view_user.php', ['user' => $user]);
    }

    public function edit(SpecificUserQueryInterface $query, $id)
    {
        // Load user data
        if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
            return Response::notFound();
        }

        // Return view
        return Response::view('edit_user.php', ['user' => $user]);
    }

    public function update(UserRepositoryInterface $repository)
    {
        // Load user model
        if (!$user = $repository->find($id)) {
            return Response::notFound();
        }

        // Update the user
        $user->first_name = $_POST['first_name'];
        $user->last_name = $_POST['last_name'];
        $user->gender = $_POST['gender'];
        $user->email = $_POST['email'];

        // Save the user
        $repository->save($user);

        // Return success
        return true;
    }

    public function delete(UserRepositoryInterface $repository)
    {
        // Load user model
        if (!$user = $repository->find($id)) {
            return Response::notFound();
        }

        // Delete the user
        $repository->delete($user);

        // Return success
        return true;
    }
}

这里需要注意的重要事项是,当我修改(创建、更新或删除)实体时,我使用的是真实的模型对象,并通过我的存储库执行持久化。

然而,当我显示(选择数据并将其发送到视图)时,我不是在使用模型对象,而是使用普通的旧值对象。我只选择我需要的字段,它的设计使我可以最大限度地发挥我的数据查找性能。

我的存储库保持非常干净,取而代之的是,这种“混乱”被组织到我的模型查询中。

我使用数据映射器来帮助开发,因为为常见任务编写重复的SQL实在是太荒谬了。但是,您完全可以在需要的地方编写SQL(复杂的查询、报告等)。当你这样做的时候,它就很好地隐藏在一个正确命名的类中了。

我很想听听你对我方法的看法!

 类似资料:
  • 问题内容: 前言:我正在尝试在MVC体系结构和关系数据库中使用存储库模式。 我最近开始学习PHP中的TDD,并且意识到我的数据库与我的其余应用程序之间的联系太紧密了。我已经阅读了有关存储库并使用IoC容器将其“注入”到控制器中的信息。很酷的东西。但是现在有一些关于存储库设计的实际问题。考虑以下示例。 问题1:字段过多 所有这些查找方法均使用全选()方法。但是,在我的应用程序中,我总是试图限制获得的

  • 关于存储库模式,我有几个问题: > 如果我只使用离线数据库(例如Room with LiveData),是否使用存储库模式? 如果我的应用程序现在离线,但将来会连接到远程数据库,那么我应该实现存储库模式还是以后再做就不会有问题了?

  • 我查阅了许多存储库设计模式教程,如 https://asperbrothers.com/blog/implement-repository-pattern-in-laravel/ https://www.larashout.com/how-to-use-repository-pattern-in-laravel https://laravelarticle.com/repository-desig

  • 我正在遵循一个足够简单的YouTube教程(链接在这里),关于使用MVC的存储库设计模式。它很好,但他使用 MVC5 和 EF6,它们对异步方法有很多支持。 我正在使用MVC4,每当我尝试将项目升级为使用EF6时都会遇到重大问题。所以我只使用EF5,但这不是问题。 我将他教程中的代码修改为不使用Async,如下所示(他的原始代码在注释中): 下面是完成一些基本实现后,接口生成的代码(存储库): 同

  • 我正在尝试学习存储库模式,似乎有点困惑,当我急于加载关系并将db逻辑排除在控制器之外时,如何使用此存储库模式。 快速概述我的存储库/应用程序结构。 示例ProductInterface。php 示例类别接口。php 好的,最简单的部分是使用DI向控制器注入模型依赖关系。 列出与相关产品的所有类别更加困难,因为我不再使用雄辩的模型。我正在使用一个界面,它没有暴露所有雄辩的方法。 如果我没有在我的El

  • 问题内容: 让我解释一下:我并不是在问将特定日期时间的时区存储在数据库中的正确方法。我说的是时区本身。例如: 我有一个名为“用户”的MySQL表。现在,在此表上,我希望有一列包含用户居住地的时区(由用户提供,将从列表中选择)。我正在使用PHP,其中包含类似以下时区字符串的列表: 美国时区列表 现在,显而易见的解决方案(至少对我而言)是在“用户”表中创建VARCHAR列,然后将PHP使用的时区字符串