Ответ 1
Давайте ответим на ваши вопросы сверху донизу и посмотрим, что я могу добавить к тому, что вы говорите.
В таблице базы данных будет указан один класс, как описано ниже.
Пользователь - класс для взаимодействия с пользовательской таблицей. Класс содержит такие функции, как createUser, updateUser и т.д.
Местоположение - класс для взаимодействия с таблицей местоположений. Класс содержит функции > такие как searchLocation, createLocation, updateLocation и т.д.
По существу у вас есть выбор здесь. Описанный метод называется активная запись. Сам объект знает, как и где он хранится. Для простых объектов, которые взаимодействуют с базой данных для создания/чтения/обновления/удаления, этот шаблон действительно полезен.
Если операции с базой данных становятся более обширными и менее понятными, часто бывает неплохо пойти с устройством сопоставления данных (например, эта реализация). Это второй объект, который обрабатывает все взаимодействия с базами данных, в то время как сам объект (например, пользователь или местоположение) обрабатывает только те операции, которые относятся к этому объекту (например, login или goToLocation). Если вы когда-нибудь захотите разместить свои объекты, вам нужно будет создать новый сопоставитель данных. Ваш объект даже не знает, что что-то изменилось в реализации. Это обеспечивает encapsulation и разделение проблем.
Существуют и другие варианты, но эти два являются наиболее распространенными способами реализации взаимодействия с базами данных.
Кроме того, я думаю о создании другого класса следующим образом: -
DatabaseHelper: класс, который будет иметь статический член, который представляет соединение с базой данных. Этот класс будет содержать методы нижнего уровня для выполнения SQL-запросов, таких как executeQuery (запрос, параметры), executeUpdate (запрос, параметры) и т.д.
То, что вы здесь описываете, выглядит как singleton. Обычно это не очень хороший дизайн. Вы действительно уверены, что никогда не будет второй базы данных? Вероятно, нет, поэтому вы не должны ограничиваться реализацией, которая допускает только одно подключение к базе данных. Вместо создания DatabaseHelper со статическими членами вы можете лучше создать объект базы данных с помощью некоторых методов, которые позволят вам подключиться, отключить, выполнить запрос и т.д. Таким образом, вы можете повторно использовать его, если вам когда-либо понадобится второе соединение.
На этом этапе у меня есть два варианта использования класса DatabaseHelper в других классах: -
- Класс User and Locations расширяет класс DatabaseHelper, чтобы они могли использовать унаследованные методы executeQuery и executeUpdate в DatabaseHelper. В этом случае DatabaseHelper будет гарантировать, что есть только один экземпляр подключения к базе данных в любой момент времени.
- Класс DatabaseHelper будет введен в класс User and Locations через класс Container, который будет создавать экземпляры пользователя и местоположения. В этом случае контейнер будет следить за тем, чтобы в приложении было только один экземпляр DatabaseHelper в любое время.
Это два подхода, которые быстро приходят мне в голову. Я хочу знать, с каким подходом идти. Возможно, оба этих подхода недостаточно хороши, и в этом случае я хочу знать любой другой подход, который я могу использовать для реализации модуля взаимодействия с базой данных.
Первый вариант не является жизнеспособным. Если вы прочитали описание наследования, вы увидите, что наследование обычно используется для создания подтипа существующего объекта. Пользователь не является подтипом DatabaseHelper и не является местоположением. MysqlDatabase будет подтипом базы данных, или Admin будет подтипом Пользователя. Я бы посоветовал против этого варианта, поскольку он не следует лучшим практикам объектно-ориентированного программирования.
Второй вариант лучше. Если вы решите использовать метод активной записи, вы действительно должны ввести базу данных в объекты "Пользователь" и "Место". Разумеется, это должно быть сделано каким-то третьим объектом, который обрабатывает все эти взаимодействия. Вероятно, вы захотите взглянуть на инъекция зависимостейи инверсия управления.
В противном случае, если вы выберете метод сопоставления данных, вы должны ввести базу данных в карту данных. Таким образом, по-прежнему можно использовать несколько баз данных, разделяя все ваши проблемы.
Для получения дополнительной информации о шаблоне активной записи и шаблоне карты данных я бы посоветовал вам получить Шаблоны архитектуры корпоративных приложений Мартина Фаулера. Он полон таких моделей и многое, многое другое!
Я надеюсь, что это поможет (и извините, если есть какие-то действительно плохие английские предложения там, я не носитель языка!).
== EDIT ==
Использование шаблона активной записи шаблона карт данных также помогает в тестировании вашего кода (например, Aurel сказал). Если вы разделите все звенья кода, чтобы сделать только одну вещь, будет легче проверить, что это действительно делает это. Используя PHPUnit (или некоторую другую структуру тестирования), чтобы проверить правильность работы вашего кода, вы можете быть уверены, что ошибки не будут присутствовать в каждом из ваших блоков кода. Если вы смешиваете проблемы (например, когда вы выбираете вариант 1 ваших вариантов), это будет намного сложнее. Все становится довольно запутанным, и вы скоро получите большую кучу кода спагетти.
== EDIT2 ==
Пример активного шаблона записи (который довольно ленив и неактивен):
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() {}
}
== EDIT 3: после редактирования bot ==
Обратите внимание, что класс Container будет содержать статический член типа DatabaseHelper. Он будет содержать частную статическую функцию getDatabaseHelper(), которая вернет существующий экземпляр DatabaseHelper или создаст новый экземпляр DatabaseHelper, если он не существует, и в этом случае он заполнит объект соединения в DatabaseHelper. Контейнер также будет содержать статические методы, называемые makeUser и makeLocation, которые будут вводить DatabaseHelper в User и Locations соответственно.
Прочитав несколько ответов, я понимаю, что на начальный вопрос почти был дан ответ. Но все же есть сомнения, что необходимо уточнить, прежде чем я смогу принять окончательный ответ, который заключается в следующем.
Что делать, когда у меня есть несколько баз данных для подключения, а не одна база данных. Как класс DatabaseHelper включает это и как контейнер вставляет соответствующие зависимости базы данных в объекты User и Location?
Я думаю, что нет никакой необходимости в каком-либо статическом свойстве, и контейнеру не нужен этот makeUser методов makeLocation. Предположим, что у вас есть точка входа в приложение, в которой вы создаете класс, который будет контролировать весь поток в вашем приложении. Кажется, вы называете это контейнером, я предпочитаю называть его контроллером. В конце концов, он контролирует, что происходит в вашем приложении.
$controller = new Controller();
Контроллер должен будет знать, какую базу данных он должен загрузить, и если есть одна база данных или несколько. Например, одна база данных содержит пользовательские данные, база данных anonther содержит данные о местоположении. Если активна активная запись пользователя выше и аналогичный класс местоположения, то контроллер может выглядеть следующим образом:
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 или что-то еще. Если у вас несколько классов контроллеров, он может окупиться, чтобы иметь другую точку входа, которая создает все базы данных и толкает их в контроллер. Например, вы можете называть это фронт-контроллером или контроллером ввода.
Отвечает ли вам ответ на вопросы?