Consider a Database interaction module written in PHP that contains classes for interacting with the database. I have not started coding the class so I won't be able to give code snippets.
There will be one class per database table as explained below.
User - A class for interacting with the user table. The class contains functions such as createUser, updateUser, etc.
Locations - A class for interacting with the locations table. The class contains functions such as searchLocation, createLocation, updateLocation, etc.
In addition, I am thinking of creating another class as follows: -
DatabaseHelper : A class that will have a member that represents the connection to the database. This class will contain the lower level methods for executing SQL queries such as executeQuery(query,parameters), executeUpdate(query,parameters) and so on.
At this point, I have two options to use the DatabaseHelper class in other classes : -
These are the two approaches that quickly come to my mind. I want to know which approach to go with. It is possible that both these approaches are not good enough, in which case, I want to know any other approach that I can go with to implement the database interaction module.
Edit:
Note that the Container class will contain a static member of type DatabaseHelper. It will contain a private static getDatabaseHelper() function that will return an existing DatabaseHelper instance or create a new DatabaseHelper instance if one does not exists in which case, it will populate the connection object in DatabaseHelper. The Container will also contain static methods called makeUser and makeLocation that will inject the DatabaseHelper into User and Locations respectively.
After reading a few answers, I realize that the initial question has almost been answered. But there is still a doubt that needs to be clarified before I can accept the final answer which is as follows.
What to do when I have multiple databases to connect to rather than a single database. How does the DatabaseHelper class incorporate this and how does the container inject appropriate database dependencies in the User and Location objects?
Lets answer your questions from top to bottom, and see what I can add to what you say.
There will be one class per database table as explained below.
User - A class for interacting with the user table. The class contains functions such as createUser, updateUser, etc.
Locations - A class for interacting with the locations table. The class contains functions >such as searchLocation, createLocation, updateLocation, etc.
Essentially you have to choices here. The method you described is called the active record pattern. The object itself knows how and where it is stored. For simple objects that interact with a database to create / read / update / delete, this pattern is really usefull.
If the database operations become more extensive and less simple to understand, it is often a good choice to go with a data mapper (eg. this implementation). This is a second object that handles all the database interactions, while the object itself (eg. User or Location) only handles operations that are specific to that object (eg. login or goToLocation). If you ever want to chance the storage of your objects, you will only have to create a new data mapper. Your object won't even know that something changed in the implementation. This enforces encapsulation and seperation of concerns.
There are other options, but these two are the most used ways to implement database interactions.
In addition, I am thinking of creating another class as follows: -
DatabaseHelper : A class that will have a static member that represents the connection to the database. This class will contain the lower level methods for executing SQL queries such as executeQuery(query,parameters), executeUpdate(query,parameters) and so on.
What you are describing here sounds like a singleton. Normally this isn't really a good design choice. Are you really, really certain that there will never be a second database? Probably not, so you should not confine yourself to an implementation that only allowes for one database connection. Instead of making a DatabaseHelper with static members, you can better create a Database object with some methods that allow you to connect, disconnect, execute a query, etc. This way you can reuse it if you ever need a second connection.
At this point, I have two options to use the DatabaseHelper class in other classes : -
- The User and Locations class will extend the DatabaseHelper class so that they can use the inherited executeQuery and executeUpdate methods in DatabaseHelper. In this case, DatabaseHelper will ensure that there is only one instance of the connection to the database at any given time.
- The DatabaseHelper class will be injected in the User and Locations class through a Container class that will make User and Location instances. In this case, the Container will make sure that there is only one instance of DatabaseHelper in the application at any given time.
These are the two approaches that quickly come to my mind. I want to know which approach to go with. It is possible that both these approaches are not good enough, in which case, I want to know any other approach that I can go with to implement the database interaction module.
The first option isn't really viable. If you read the description of inheritance, you will see that inheritance is normally used to create a subtype of an existing object. An User is not a subtype of a DatabaseHelper, nor is a location. A MysqlDatabase would be a subtype of a Database, or a Admin would be a subtype of an User. I would advise against this option, as it isn't following the best practices of object oriented programming.
The second option is better. If you choose to use the active record method, you should indeed inject the Database into the User and Location objects. This should of course be done by some third object that handles all these kind of interactions. You will probably want to take a look at dependency injection and inversion of control.
Otherwise, if you choose the data mapper method, you should inject the Database into the data mapper. This way it is still possible to use several databases, while seperating all your concerns.
For more information about the active record pattern and the data mapper pattern, I would advise you to get the Patterns of Enterprise Application Architecture book of Martin Fowler. It is full of these kind of patterns and much, much more!
I hope this helps (and sorry if there are some really bad English sentences in there, I'm not a native speaker!).
== EDIT ==
Using the active record pattern of data mapper pattern also helps in testing your code (like Aurel said). If you seperate all peaces of code to do just one thing, it will be easier to check that it is really doing this one thing. By using PHPUnit (or some other testing framework) to check that your code is properly working, you can be pretty sure that no bugs will be present in each of your code units. If you mix up the concerns (like when you choose option 1 of your choices), this will be a whole lot harder. Things get pretty mixed up, and you will soon get a big bunch of spaghetti code.
== EDIT2 ==
An example of the active record pattern (that is pretty lazy, and not really active):
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() {}
}
And an example of the data mapper pattern:
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: after edit by bot ==
Note that the Container class will contain a static member of type DatabaseHelper. It will contain a private static getDatabaseHelper() function that will return an existing DatabaseHelper instance or create a new DatabaseHelper instance if one does not exists in which case, it will populate the connection object in DatabaseHelper. The Container will also contain static methods called makeUser and makeLocation that will inject the DatabaseHelper into User and Locations respectively.
After reading a few answers, I realize that the initial question has almost been answered. But there is still a doubt that needs to be clarified before I can accept the final answer which is as follows.
What to do when I have multiple databases to connect to rather than a single database. How does the DatabaseHelper class incorporate this and how does the container inject appropriate database dependencies in the User and Location objects?
I think there is no need for any static property, nor does the Container need those makeUser of makeLocation methods. Lets assume that you have some entry point of your application, in which you create a class that will control all flow in your application. You seem to call it a container, I prefer to call it a controller. After all, it controls what happens in your application.
$controller = new Controller();
The controller will have to know what database it has to load, and if there is one single database or multiple ones. For example, one database contains the user data, anonther database contains the location data. If the active record User from above and a similar Location class are given, then the controller might look as follows:
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;
}
}
Probably it would be good to move all the echo's to a View class or something. If you have multiple controller classes, it might pay off to have a different entrypoint that creates all databases and pushes them in the controller. You could for example call this a front controller or an entry controller.
Does this answer you open questions?
I would go with the dependancy injection, for the following reason: if at some point you want to write tests for your applications, it will allow you to replace the DatabaseHelper instance by a stub class, implementing the same interface but that do not really access a database. This will make it really easier to test your model functionalities.
By the way, for this to be really useful, your other classes (User, Locations) should depend on a DatabaseHelperInterface rather than directly on DatabaseHelper. (This is required to be able to switch implementations)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With