前言:我正在尝试在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 *
)方法。但是,在我的应用程序中,我总是试图限制获得的字段数,因为这通常会增加开销并减慢运行速度。对于使用这种模式的人,您该如何处理?
尽管该类现在看起来不错,但我知道在现实世界中的应用程序中,我需要更多的方法。例如:
如您所见,可能的方法列表可能非常长。然后,如果您添加了上面的字段选择问题,问题将会恶化。过去,我通常只是将所有这些逻辑放在控制器中:
<?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))
}
}
我看到了将接口用于存储库的好处,因此我可以换出我的实现(出于测试目的或其他目的)。我对接口的理解是,它们定义了实现必须遵循的契约。除非您开始向存储库中添加其他方法,否则这非常有用findAllInCountry()
。现在,我需要更新我的界面,使其也具有此方法,否则,其他实现可能没有它,这可能会破坏我的应用程序。这样感觉很疯狂……尾巴摇晃着狗。
这使我相信,库应该只有方法固定数量(如save()
,remove()
,find()
,findAll()
,等)。但是,我该如何运行特定的查找?我听说过规范模式,但是在我看来,这只会减少整个记录集(通过IsSatisfiedBy()
),如果从数据库中提取数据,显然会遇到主要的性能问题。
显然,在使用存储库时,我需要重新考虑一下。任何人都可以启发一下如何最好地处理它吗?
我以为我会努力回答自己的问题。接下来的内容只是解决我原始问题1-3中的一种方法。
免责声明:在描述模式或技术时,我可能并不总是使用正确的术语。 抱歉
Users
。我将持久性存储(数据库)交互分为两类: R (读取)和 CUD
(创建,更新,删除)。我的经验是,读取确实是导致应用程序运行缓慢的原因。而且,尽管数据处理(CUD)实际上较慢,但它发生的频率却要低得多,因此也就少了很多问题。
CUD
(创建,更新,删除)很容易。这将涉及使用实际模型,然后将其传递给我Repositories
以保持持久性。请注意,我的存储库仍将提供Read方法,但仅用于对象创建而不是显示。以后再说。
R
(读取)不是那么容易。这里没有模型,只是重视对象。如果愿意,请使用数组。这些对象可能代表单个模型或许多模型的混合,实际上是任何东西。它们本身并不是很有趣,但是它们是如何产生的。我正在使用我所说的QueryObjects
。
让我们从基本的用户模型开始简单。请注意,根本没有ORM扩展或数据库内容。只是纯粹的模特荣耀。添加您的getter,setter,validation等。
class User
{
public $id;
public $first_name;
public $last_name;
public $gender;
public $email;
public $password;
}
在创建用户存储库之前,我想创建我的存储库界面。这将定义存储库必须遵循的“合同”,以便供我的控制器使用。记住,我的控制器将不知道数据的实际存储位置。
请注意,我的存储库将只包含这三种方法。该save()
方法仅根据用户对象是否设置了ID来负责创建和更新用户。
interface UserRepositoryInterface
{
public function find($id);
public function save(User $user);
public function remove(User $user);
}
现在创建我的接口实现。如前所述,我的示例将使用SQL数据库。请注意,使用数据映射器可以避免编写重复的SQL查询。
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(读取)。查询对象只是某种类型的数据查找逻辑的封装。他们不是查询生成器。通过像我们的存储库一样抽象它,我们可以更改它的实现并对其进行测试。查询对象的示例可能是AllUsersQuery
orAllActiveUsersQuery
或什至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(复杂的查询,报告等)。当您这样做时,它会很好地藏在一个适当命名的类中。
我很想听听您对我的做法的看法!
2015年7月更新:
我在评论中被问到我所有这些都以什么结尾。好吧,实际上相差不远。说实话,我还是不太喜欢存储库。我发现它们对于基本的查询(特别是如果您已经在使用ORM)来说是多余的,并且在处理更复杂的查询时显得凌乱。
我通常使用ActiveRecord样式的ORM,因此大多数情况下,我只会在整个应用程序中直接引用这些模型。但是,在查询比较复杂的情况下,我将使用查询对象使这些查询更可重用。我还应注意,我总是将模型注入方法中,从而使它们在测试中更易于模拟。
前言:我试图在关系数据库的MVC体系结构中使用存储库模式。 我最近开始学习PHP中的TDD,我意识到我的数据库与应用程序的其余部分耦合得太紧密了。我读过关于存储库和使用IoC容器将其“注入”到我的控制器中的文章。很酷的东西。但现在有一些关于存储库设计的实际问题。请考虑以下示例。 所有这些查找方法都使用选择所有字段()方法。然而,在我的应用程序中,我总是试图限制我得到的字段的数量,因为这经常增加开销
关于存储库模式,我有几个问题: > 如果我只使用离线数据库(例如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
问题内容: 让我解释一下:我并不是在问将特定日期时间的时区存储在数据库中的正确方法。我说的是时区本身。例如: 我有一个名为“用户”的MySQL表。现在,在此表上,我希望有一列包含用户居住地的时区(由用户提供,将从列表中选择)。我正在使用PHP,其中包含类似以下时区字符串的列表: 美国时区列表 现在,显而易见的解决方案(至少对我而言)是在“用户”表中创建VARCHAR列,然后将PHP使用的时区字符串
我正在遵循一个足够简单的YouTube教程(链接在这里),关于使用MVC的存储库设计模式。它很好,但他使用 MVC5 和 EF6,它们对异步方法有很多支持。 我正在使用MVC4,每当我尝试将项目升级为使用EF6时都会遇到重大问题。所以我只使用EF5,但这不是问题。 我将他教程中的代码修改为不使用Async,如下所示(他的原始代码在注释中): 下面是完成一些基本实现后,接口生成的代码(存储库): 同
我正在尝试学习存储库模式,似乎有点困惑,当我急于加载关系并将db逻辑排除在控制器之外时,如何使用此存储库模式。 快速概述我的存储库/应用程序结构。 示例ProductInterface。php 示例类别接口。php 好的,最简单的部分是使用DI向控制器注入模型依赖关系。 列出与相关产品的所有类别更加困难,因为我不再使用雄辩的模型。我正在使用一个界面,它没有暴露所有雄辩的方法。 如果我没有在我的El