让我们从上到下回答您的问题,看看我可以在您所说的内容中添加什么。
每个数据库表将有一个类,如下所述。
User - 与用户表交互的类。该类包含createUser、updateUser等函数。
Locations - 用于与位置表交互的类。该类包含函数>如searchLocation、createLocation、updateLocation等。
基本上你必须在这里选择。您描述的方法称为活动记录模式。对象本身知道它的存储方式和存储位置。对于与数据库交互以创建/读取/更新/删除的简单对象,这种模式非常有用。
如果数据库操作变得更广泛且不那么容易理解,那么使用数据映射器(例如这个实现)通常是一个不错的选择。这是处理所有数据库交互的第二个对象,而对象本身(例如用户或位置)仅处理特定于该对象的操作(例如登录或 goToLocation)。如果你想存储你的对象,你只需要创建一个新的数据映射器。您的对象甚至不知道在实现中发生了某些变化。这加强了关注点的封装和分离。
还有其他选项,但这两种是实现数据库交互的最常用方法。
另外,我正在考虑创建另一个类,如下所示:-
DatabaseHelper :一个类,它有一个静态成员,表示与数据库的连接。此类将包含用于执行 SQL 查询的较低级别的方法,例如 executeQuery(query,parameters)、executeUpdate(query,parameters) 等。
您在此处描述的内容听起来像是单例。通常这不是一个好的设计选择。你真的,真的确定永远不会有第二个数据库吗?可能不会,因此您不应该将自己局限于只允许一个数据库连接的实现。除了使用静态成员创建 DatabaseHelper 之外,您还可以使用一些方法更好地创建 Database 对象,这些方法允许您连接、断开连接、执行查询等。这样,如果您需要第二个连接,您可以重用它。
此时,我有两个选项可以在其他类中使用 DatabaseHelper 类:-
- User 和 Locations 类将扩展 DatabaseHelper 类,以便它们可以在 DatabaseHelper 中使用继承的 executeQuery 和 executeUpdate 方法。在这种情况下,DatabaseHelper 将确保在任何给定时间只有一个与数据库的连接实例。
- DatabaseHelper 类将通过 Container 类注入到 User 和 Locations 类中,该 Container 类将创建 User 和 Location 实例。在这种情况下,容器将确保在任何给定时间应用程序中只有一个 DatabaseHelper 实例。
这是我很快想到的两种方法。我想知道采用哪种方法。可能这两种方法都不够好,在这种情况下,我想知道我可以用来实现数据库交互模块的任何其他方法。
第一种选择实际上并不可行。如果你阅读了继承的描述,你会看到继承通常用于创建现有对象的子类型。用户不是 DatabaseHelper 的子类型,也不是位置。MysqlDatabase 将是 Database 的子类型,或者 Admin 将是 User 的子类型。我建议不要使用此选项,因为它没有遵循面向对象编程的最佳实践。
第二种选择更好。如果您选择使用活动记录方法,您确实应该将数据库注入到用户和位置对象中。这当然应该由处理所有这些交互的第三个对象来完成。您可能想看看依赖注入和控制反转。
否则,如果您选择数据映射器方法,则应将数据库注入数据映射器。这样,仍然可以使用多个数据库,同时分离所有关注点。
有关活动记录模式和数据映射器模式的更多信息,我建议您获取Martin Fowler的企业应用程序架构模式一书。它充满了这些模式以及更多!
我希望这会有所帮助(如果那里有一些非常糟糕的英语句子,我很抱歉,我不是母语人士!)。
== 编辑 ==
使用数据映射器模式的活动记录模式也有助于测试您的代码(如 Aurel 所说)。如果您将所有代码分开来只做一件事,那么检查它是否真的在做这件事会更容易。通过使用PHPUnit(或其他一些测试框架)来检查您的代码是否正常工作,您可以非常确定每个代码单元中都不会出现错误。如果您混淆了这些问题(例如当您选择选项 1 时),这将变得更加困难。事情变得非常混乱,你很快就会得到一大堆意大利面条代码。
== 编辑2 ==
活动记录模式的示例(非常懒惰,并不是真正的活动):
class Controller {
public function main() {
$database = new Database('host', 'username', 'password');
$database->selectDatabase('database');
$user = new User($database);
$user->name = 'Test';
$user->insert();
$otherUser = new User($database, 5);
$otherUser->delete();
}
}
class Database {
protected $connection = null;
public function __construct($host, $username, $password) {
// Connect to database and set $this->connection
}
public function selectDatabase($database) {
// Set the database on the current connection
}
public function execute($query) {
// Execute the given query
}
}
class User {
protected $database = null;
protected $id = 0;
protected $name = '';
// Add database on creation and get the user with the given id
public function __construct($database, $id = 0) {
$this->database = $database;
if ($id != 0) {
$this->load($id);
}
}
// Get the user with the given ID
public function load($id) {
$sql = 'SELECT * FROM users WHERE id = ' . $this->database->escape($id);
$result = $this->database->execute($sql);
$this->id = $result['id'];
$this->name = $result['name'];
}
// Insert this user into the database
public function insert() {
$sql = 'INSERT INTO users (name) VALUES ("' . $this->database->escape($this->name) . '")';
$this->database->execute($sql);
}
// Update this user
public function update() {
$sql = 'UPDATE users SET name = "' . $this->database->escape($this->name) . '" WHERE id = ' . $this->database->escape($this->id);
$this->database->execute($sql);
}
// Delete this user
public function delete() {
$sql = 'DELETE FROM users WHERE id = ' . $this->database->escape($this->id);
$this->database->execute($sql);
}
// Other method of this user
public function login() {}
public function logout() {}
}
以及数据映射器模式的示例:
class Controller {
public function main() {
$database = new Database('host', 'username', 'password');
$database->selectDatabase('database');
$userMapper = new UserMapper($database);
$user = $userMapper->get(0);
$user->name = 'Test';
$userMapper->insert($user);
$otherUser = UserMapper(5);
$userMapper->delete($otherUser);
}
}
class Database {
protected $connection = null;
public function __construct($host, $username, $password) {
// Connect to database and set $this->connection
}
public function selectDatabase($database) {
// Set the database on the current connection
}
public function execute($query) {
// Execute the given query
}
}
class UserMapper {
protected $database = null;
// Add database on creation
public function __construct($database) {
$this->database = $database;
}
// Get the user with the given ID
public function get($id) {
$user = new User();
if ($id != 0) {
$sql = 'SELECT * FROM users WHERE id = ' . $this->database->escape($id);
$result = $this->database->execute($sql);
$user->id = $result['id'];
$user->name = $result['name'];
}
return $user;
}
// Insert the given user
public function insert($user) {
$sql = 'INSERT INTO users (name) VALUES ("' . $this->database->escape($user->name) . '")';
$this->database->execute($sql);
}
// Update the given user
public function update($user) {
$sql = 'UPDATE users SET name = "' . $this->database->escape($user->name) . '" WHERE id = ' . $this->database->escape($user->id);
$this->database->execute($sql);
}
// Delete the given user
public function delete($user) {
$sql = 'DELETE FROM users WHERE id = ' . $this->database->escape($user->id);
$this->database->execute($sql);
}
}
class User {
public $id = 0;
public $name = '';
// Other method of this user
public function login() {}
public function logout() {}
}
== 编辑 3:由机器人编辑后 ==
请注意,Container 类将包含一个类型为 DatabaseHelper 的静态成员。它将包含一个私有静态 getDatabaseHelper() 函数,该函数将返回现有的 DatabaseHelper 实例或创建一个新的 DatabaseHelper 实例(如果不存在),在这种情况下,它将填充 DatabaseHelper 中的连接对象。Container 还将包含名为 makeUser 和 makeLocation 的静态方法,它们将分别将 DatabaseHelper 注入到 User 和 Locations 中。
在阅读了几个答案后,我意识到最初的问题几乎已经得到了回答。但在我接受如下最终答案之前,仍有一个疑问需要澄清。
当我有多个数据库要连接而不是单个数据库时该怎么办。DatabaseHelper 类如何结合这一点以及容器如何在 User 和 Location 对象中注入适当的数据库依赖项?
我认为不需要任何静态属性,Container 也不需要那些 makeUser 的 makeLocation 方法。让我们假设您有一些应用程序的入口点,您可以在其中创建一个类来控制应用程序中的所有流程。你似乎称它为容器,我更喜欢称它为控制器。毕竟,它控制着你的应用程序中发生的事情。
$controller = new Controller();
控制器必须知道它必须加载哪个数据库,以及是单个数据库还是多个数据库。例如,一个数据库包含用户数据,另一个数据库包含位置数据。如果给出了上面的活动记录 User 和类似的 Location 类,那么控制器可能如下所示:
class Controller {
protected $databases = array();
public function __construct() {
$this->database['first_db'] = new Database('first_host', 'first_username', 'first_password');
$this->database['first_db']->selectDatabase('first_database');
$this->database['second_db'] = new Database('second_host', 'second_username', 'second_password');
$this->database['second_db']->selectDatabase('second_database');
}
public function showUserAndLocation() {
$user = new User($this->databases['first_database'], 3);
$location = $user->getLocation($this->databases['second_database']);
echo 'User ' . $user->name . ' is at location ' . $location->name;
}
public function showLocation() {
$location = new Location($this->database['second_database'], 5);
echo 'The location ' . $location->name . ' is ' . $location->description;
}
}
将所有回声移动到 View 类或其他东西可能会很好。如果您有多个控制器类,则可能有一个不同的入口点来创建所有数据库并将它们推送到控制器中。例如,您可以将其称为前端控制器或入口控制器。
这是否回答了您开放的问题?