One of the things about creating dynamic web apps, it's having to deal with a database.
In an MVC framework, models are in charge of communicating with the database. To deal with the DB ting, we defined a Model
class that will take care of connecting to the database, and even setting it up the first time the application is deployed. A good place for the Model.class.php
is the app/core
folder, next to the Router.class.php
file.
According to the 42 PHP styleguide, files containing classes should be capitalized, with the
.class.php
extension.
As per project requirements, we had to create a config
folder containing:
- A
database.php
file with a bunch of globals for storing database connection settings. - A
setup.php
file capable of creating the database.
First thing we do is to require the configuration files at the top of Model
.
One of the project's requirements was to use PDO (short for PHP Data Objects), which is a PHP extension that falls into the Database Access Abstraction Layer category (it's not an ORM).
Check (The only proper) PDO tutorial for more information.
PDO provides a consistent API for interacting with several databases engines (database agnostic) using PHP code. That means that PDO alone is not enough to interact with databases from PHP. We also need a database driver in order to talk to the database server. These drivers are specific to the database engine we intend to use, meaning that if we want to interact with MySQL, we need to use the PDO_MYSQL driver.
Database operations are expensive, on top of that, if we're not careful about where and how we're connecting to our database, we may hit a too many connections error, and even crash the database engine. Where not to connect to a database:
- In the model constructors, which would cause a new connection each time a model is instantiated.
- Not even in the
Model
superclass constructor, for the same reason.
What I ended up doing is defining a static method named getDB()
, and within it, declared another static variable named dbh
.
There's nothing special about the name
dbh
, short for database handler, it's just the name used in the PHP docs.
Whenever a method in a model instance needed to perform any database operation, it would call the static::getDB()
method, which would only create a new connection if none existed.
Such a connection is represented by an instance of the PDO
class. To create a connection, the PDO constructor needs the following arguments:
- What is known as a Data Source Name (more about it later).
- Username.
- Password.
- An array of options.
When attempting to connect, we may hit a problem and an exception may be raised. To handle that, we must wrap any connection attempt in a try-catch
statement. If an exception is raised in the try
, in the catch
we check using the getCode() method the exception code. If the code is 1049
, that means there's no database yet, so we set up one in the catch
clause. We use an external file that contains of the SQL statements needed for creating the db with tables and all.
One of the arguments required by the PDO
constructor is a DSN, which is a string where we define several things:
-
The database driver.
-
The host, which in my case is the name of the Docker container where the MySQL service is running.
-
And optionally:
- The name of an existing database.
- The charset, also optional.
- The port, optional as well.
- The unix_socket, which as you may have guessed, it's optional.
In our getDB()
function we used two different dsn
strings:
- In the
try
clause we were trying to connect to an existing database, so we must specify the db name. - But in the
catch
clause, we check for a non-existing database exception. In this case, we have to omit the database name, obviously!
The abstract Model
class is a good place to define methods that will be used later on by the models to retrieve and store data from and to the database. Some actions common to all models would be:
- Perform an SQL query that returns one of something (a user, a
dickpic, etc). - An SQL query that returns all of something (a list of posts, a list of comments, etc).
- Another one query that returns true if a record is found in a table; false otherwise.
I hope you get the idea ;-)
Let's say our app is gonna have users (duh!), and we need a User
model. We could test out the getDB()
method inside a getUsers()
mmmmethod:
private $db;
public function __construct()
{
$this->db = static::getDB();
}
public function getUsers()
{
$stmt = $this->db->query('SELECT * FROM users');
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
The method above would return a list of users that a given controller would use to pass it down to a view. The static getDB()
superclass method opens a gate to the database that is use in the User
model to extract whatever data we need.