Respondamos sus preguntas de arriba a abajo, y veamos qué puedo agregar a lo que dices.
Habrá una clase por tabla de base de datos como se explica a continuación.
Usuario: una clase para interactuar con la tabla de usuarios. La clase contiene funciones como createUser, updateUser, etc.
Ubicaciones: una clase para interactuar con la tabla de ubicaciones. La clase contiene funciones> tales como searchLocation, createLocation, updateLocation, etc.
Esencialmente tiene que elegir aquí. El método que describió se llama el patrón active record. El objeto en sí sabe cómo y dónde se almacena. Para objetos simples que interactúan con una base de datos para crear/leer/actualizar/eliminar, este patrón es realmente útil.
Si las operaciones de la base de datos se vuelven más extensas y menos fáciles de entender, a menudo es una buena opción ir con un asignador de datos (por ejemplo, this implementation). Este es un segundo objeto que maneja todas las interacciones de la base de datos, mientras que el objeto mismo (por ejemplo, Usuario o Ubicación) solo maneja operaciones que son específicas de ese objeto (por ejemplo, login o goToLocation). Si alguna vez quieres tener la oportunidad de almacenar tus objetos, solo tendrás que crear un nuevo mapeador de datos. Su objeto ni siquiera sabrá que algo cambió en la implementación. Esto aplica encapsulation y seperation of concerns.
Existen otras opciones, pero estas dos son las formas más utilizadas para implementar las interacciones de la base de datos.
Además, estoy pensando en la creación de otra clase de la siguiente manera: -
DatabaseHelper: Una clase que tendrá un miembro estático que representa la conexión con la base de datos. Esta clase contendrá los métodos de nivel inferior para ejecutar consultas SQL como executeQuery (consulta, parámetros), executeUpdate (consulta, parámetros), etc.
Lo que está describiendo aquí suena como singleton. Normalmente, esta no es realmente una buena opción de diseño. ¿De verdad estás realmente seguro de que nunca habrá una segunda base de datos? Probablemente no, por lo que no debe limitarse a una implementación que solo permita una conexión de base de datos. En lugar de crear un DatabaseHelper con miembros estáticos, puede crear mejor un objeto de base de datos con algunos métodos que le permitan conectarse, desconectarse, ejecutar una consulta, etc. De esta forma, puede reutilizarlo si alguna vez necesita una segunda conexión.
En este punto, tengo dos opciones para utilizar la clase DatabaseHelper en otras clases: -
- La clase de usuario y lugares se extenderá la clase DatabaseHelper para que puedan utilizar el executeQuery heredada y executeUpdate métodos en DatabaseHelper. En este caso, DatabaseHelper se asegurará de que haya solo una instancia de la conexión a la base de datos en un momento dado.
- La clase DatabaseHelper se inyectará en la clase User and Locations a través de una clase Container que creará las instancias User y Location. En este caso, el contenedor se asegurará de que haya solo una instancia de DatabaseHelper en la aplicación en cualquier momento dado.
Estos son los dos enfoques que rápidamente vienen a mi mente. Quiero saber qué enfoque seguir. Es posible que estos dos enfoques no sean lo suficientemente buenos, en cuyo caso, deseo conocer cualquier otro enfoque que pueda seguir para implementar el módulo de interacción con la base de datos.
La primera opción no es realmente viable. Si lees el description of inheritance, verás que la herencia se usa normalmente para crear un subtipo de un objeto existente. Un usuario no es un subtipo de un DatabaseHelper, ni es una ubicación. Una MysqlDatabase sería un subtipo de una base de datos, o un administrador sería un subtipo de un usuario. Recomendaría esta opción, ya que no sigue las mejores prácticas de programación orientada a objetos.
La segunda opción es mejor. Si elige usar el método de registro activo, debe inyectar la Base de datos en los objetos Usuario y Ubicación. Por supuesto, esto debería hacerse con un tercer objeto que maneje todo este tipo de interacciones. Es probable que desee echar un vistazo a dependency injection y inversion of control.
De lo contrario, si elige el método del correlacionador de datos, debe inyectar la base de datos en el correlacionador de datos. De esta manera, aún es posible usar varias bases de datos, mientras se separan todas sus preocupaciones.
Para obtener más información sobre el patrón de registro activo y el patrón del correlacionador de datos, le aconsejo que obtenga el libro Patterns of Enterprise Application Architecture de Martin Fowler. ¡Está lleno de este tipo de patrones y mucho, mucho más!
Espero que esto ayude (y siento si hay algunas oraciones en inglés realmente malas allí, ¡no soy un hablante nativo!).
== == EDITAR
Utilizando el patrón de registro activo del patrón de asignador de datos también ayuda a probar su código (como dijo Aurel). Si separa todas las peaces de código para hacer una sola cosa, será más fácil comprobar que realmente está haciendo esto. Al usar PHPUnit (o algún otro marco de prueba) para verificar que su código funcione correctamente, puede estar bastante seguro de que no habrá errores en cada una de sus unidades de código. Si mezclas las preocupaciones (como cuando eliges la opción 1 de tus elecciones), esto será mucho más difícil. Las cosas se mezclan bastante, y pronto obtendrás un gran grupo de spaghetti code.
== == Edit2
Un ejemplo del patrón de registro activo (que es bastante lento y no muy activo):
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() {}
}
Y un ejemplo del patrón de asignador de datos:
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() {}
}
== Datos 3: después de edición == bot
Tenga en cuenta que la clase Container contendrá un miembro estático del tipo DatabaseHelper. Contendrá una función privada static getDatabaseHelper() que devolverá una instancia DatabaseHelper existente o creará una nueva instancia DatabaseHelper si no existe, en cuyo caso, completará el objeto de conexión en DatabaseHelper. El Contenedor también contendrá métodos estáticos llamados makeUser y makeLocation que inyectarán el DatabaseHelper en User y Locations respectivamente.
Después de leer algunas respuestas, me doy cuenta de que la pregunta inicial casi ha sido respondida. Pero todavía hay una duda que debe aclararse antes de poder aceptar la respuesta final, que es la siguiente.
Qué hacer cuando tengo varias bases de datos para conectarme en lugar de una única base de datos. ¿Cómo incorpora la clase DatabaseHelper esto y cómo inyecta el contenedor las dependencias apropiadas de la base de datos en los objetos User y Location?
Creo que no hay necesidad de ninguna propiedad estática, ni el Contenedor necesita esos métodos makeUser de makeLocation. Supongamos que tiene un punto de entrada de su aplicación, en el cual crea una clase que controlará todo el flujo en su aplicación. Parece que lo llamas contenedor, prefiero llamarlo controlador. Después de todo, controla lo que sucede en su aplicación.
$controller = new Controller();
El controlador tendrá que saber qué base de datos tiene que cargar, y si hay una única base de datos o varias. Por ejemplo, una base de datos contiene los datos de usuario, otra base de datos contiene los datos de ubicación. Si el usuario activa registro desde arriba y una clase ubicación similar se da, entonces el controlador puede tener un aspecto como el siguiente:
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;
}
}
Probablemente sería bueno para mover todos los de eco a una clase View o algo así. Si tiene varias clases de controlador, puede ser útil tener un punto de entrada diferente que cree todas las bases de datos y las introduzca en el controlador. Por ejemplo, podría llamarlo controlador frontal o controlador de entrada.
¿Responde a esta pregunta usted abre preguntas?
+1 Buena pregunta – Sarfraz
inheritance significa una relación 'is-a' entre las clases. El 'Usuario' no es un' DatabaseHelper'. Por lo tanto, la herencia no debe ser utilizada. Entonces, lo que tiene es la opción de composición: use DatabaseHelper como una variable miembro que cree dentro del constructor o use la inyección del constructor. – jgauffin
¿Puede el infractor por favor dejar un comentario y decirme cuál es el problema con mi pregunta? Tengo un fuerte sentimiento de que esto es un voto de odio. – CKing